diff --git a/fec/src/lowerexp.c b/fec/src/lowerexp.c index 2b97662..e457fa7 100644 --- a/fec/src/lowerexp.c +++ b/fec/src/lowerexp.c @@ -78,6 +78,28 @@ Slot lower_expr_core(Lower *L, FeNode *n) if (n->text && (!strcmp(n->text, "and") || !strcmp(n->text, "or"))) return lower_logical(L, n, !strcmp(n->text, "and")); op = binary_op(n->text, &is_cmp); + /* Comparing an optional with `null` asks about its tag, not about the + bytes of the whole wrapper -- which has no value form at all. */ + if ((op == FE_IR_EQ || op == FE_IR_NE) && n->a && n->b) { + FeNode *w = fe_m7_is_null(n->b) ? n->a : + (fe_m7_is_null(n->a) ? n->b : 0); + FeType *wt = w ? w->sem_type : 0; + if (wt && wt->kind == FE_TYPE_OPTIONAL) { + Slot s = lower_expr(L, w); + unsigned t0; + unsigned z; + if (!s.is_place) { + fail(L, "an optional with no place", w); + return slot_void(); + } + t0 = wrapper_tag(L, s, wt, w); + z = fe_ir_const(L->m, L->b, + uses_niche(wt) ? FE_IR_PTR : FE_IR_I8, 0); + return slot_value(fe_ir_binary(L->m, L->b, op, + uses_niche(wt) ? FE_IR_PTR : FE_IR_I8, t0, z, 1), + FE_IR_I8); + } + } operand = ir_type(n->a ? n->a->sem_type : 0); if (operand == FE_IR_VOID || operand == FE_IR_MEM) operand = FE_IR_I32; a = as_value(L, lower_expr(L, n->a), n->a); diff --git a/fec/src/lowerstm.c b/fec/src/lowerstm.c index cfc5f5d..33665e3 100644 --- a/fec/src/lowerstm.c +++ b/fec/src/lowerstm.c @@ -344,10 +344,20 @@ void lower_if_let(Lower *L, FeNode *n) FeType *bt = binding->sem_type; Slot payload = wrapper_payload(L, value, opt); unsigned local = declare_var(L, binding->cname, bt, binding->text); - if (bt && (bt->kind == FE_TYPE_REF || bt->kind == FE_TYPE_RAW)) - fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), - as_address(L, payload, n), FE_IR_PTR); - else + if (bt && (bt->kind == FE_TYPE_REF || bt->kind == FE_TYPE_RAW)) { + /* The binding is a reference either way, but for two different + reasons. When the payload is itself a single pointer (`^T`, + `&T`) the binding *is* that pointer, so it has to be read out. + When the payload is a value the binding points at where it sits + inside the wrapper, so the address is what is wanted. Taking the + address in the first case gives a pointer to the pointer, and + the program reads an address where it expects a value. */ + FeType *pl = opt ? (opt->kind == FE_TYPE_ERROR_UNION + ? opt->error_value : opt->elem) : 0; + unsigned p = pl && ir_type(pl) == FE_IR_PTR + ? as_value(L, payload, n) : as_address(L, payload, n); + fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), p, FE_IR_PTR); + } else store_into(L, fe_ir_at_local(local, 0), payload, n, ir_size(bt)); } lower_stmt(L, n->b); diff --git a/fec/tests/exec/optref.fe b/fec/tests/exec/optref.fe new file mode 100644 index 0000000..50725ae --- /dev/null +++ b/fec/tests/exec/optref.fe @@ -0,0 +1,93 @@ +// EXIT:0 +// OUTPUT:int 5 none 0 +// OUTPUT:own 5 absent -1 +// OUTPUT:ref 5 absent yes +// OUTPUT:cmp yes no +// OUTPUT:unwrap 5 +// OUTPUT:balanced +unit optref; + +import std.io; +import std.sys; +import std.mem; + +// An optional whose payload cannot be null keeps no separate tag: the payload's +// own impossible value is `null`. `?i32` carries a tag; `?^T` and `?&T` do not. +// +// Both shapes have to answer the same questions, and the pointer-shaped ones +// were answering with the address of the slot the pointer sits in rather than +// with the pointer -- one level of indirection too many, in code that compiled +// and ran. + +fn opt_int(on: bool) -> ?i32 { + if on { return 5; } + return null; +} + +/// A niche optional over an owner. The `if let` binding is `&i32` -- the +/// pointer itself, not where it is kept -- and `.?` moves the owner out so it +/// can be let go of. +fn take(o: ?^i32) -> i32 { + var box: ?^i32 = o; + var v: i32 = -1; + if let Some(p) = box { v = p.^; } + if box == null { return v; } + // SPEC 5 R7: a non-Copy value leaves a projection through `mem.replace`, + // which puts something valid back where it was. + let owner: ^i32 = mem.replace(&mut box, null).?; + mem.destroy(owner); + return v; +} + +fn make(v: i32) -> ?^i32 { + let p: ^i32 = mem.create(v) catch |e| { return null; }; + return p; +} + +struct Bag { + items: ^[]mut i32, + + /// SPEC 5 R8(a): derived from `self`, so the borrow is the caller's. + fn at(self: &Self, i: usize) -> ?&i32 { + if i >= self.items.^.n { return null; } + return &self.items.^[i]; + } +} + +fn run() -> !void { + // A tag in front of the payload. + var got: i32 = 0; + if let Some(a) = opt_int(true) { got = a; } + let none: i32 = opt_int(false) orelse 0; + @print("int {} none {}\n", got, none); + + // A niche over `^i32`. + @print("own {} absent {}\n", take(make(5)), take(null)); + + // A niche over `&i32`. + let room: ^[]mut i32 = try mem.alloc_slice(i32, 2); + room.^[0] = 5; + let b: Bag = Bag{ items: room }; + var seen: i32 = 0; + if let Some(r) = b.at(0) { seen = r.^; } + var past: bool = false; + if let Some(r) = b.at(9) { seen = seen; } else { past = true; } + @print("ref {} absent {}\n", seen, yesno(past)); + + // Comparing against null reads the tag, not the bytes of the wrapper. + @print("cmp {} {}\n", yesno(b.at(9) == null), yesno(b.at(0) == null)); + @print("unwrap {}\n", b.at(0).?.^); + return; +} + +fn yesno(b: bool) -> []u8 { + if b { return "yes"; } + return "no"; +} + +fn main() -> i32 { + run() catch |e| { @print("failed\n"); return 1; }; + if sys.allocs() == sys.frees() { @print("balanced\n"); } + else { @print("leaked\n"); } + return 0; +}