fix: struct-literal → optional coercion + #get through optional chain (issue 0160)
Two fixes for optional interactions surfaced by the #set/#get review. The
original issue 0160 mis-diagnosed (A) as an optional-chain bug; the chain works
fine for real fields. The actual bugs:
(A) A bare struct literal `.{ ... }` against an optional target `?T` was built
into the optional's {payload, has_value} layout instead of the inner T, then
re-wrapped — corrupting the value (a multi-field payload's first field clobbered
by the has_value flag, or a `?T` arg silently null) or failing LLVM
verification. lowerStructLiteral now builds the inner T, materializes it, and
wraps via coerceToType; lowerVarDecl's previously-UNCONDITIONAL optional wrap is
guarded so an already-`?T` value isn't double-wrapped. Fixed across var-decl,
arg, return, nested field, reassignment, and array-element contexts.
(B) `#get` accessors are now reachable through an optional chain (`obj?.getter`):
lowerOptionalChain dispatches the getter via a synthetic receiver, and
expr_typer types `obj?.getter` through a shared getterReturnTypeOnDeref helper
(handles `?T` and `?*T`, value and pointer optionals, and generic-instance
getters like List.len). The `#set` write side through `?.` is intentionally left
matching real-field behavior (optional-chain assignment unsupported).
Regression tests: examples/optionals/0906 (struct-literal → optional) and 0907
(accessor through chain). issues/0160 marked RESOLVED with the corrected root
cause.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
// A bare struct literal `.{ ... }` against an optional target `?T` builds the
|
||||
// inner `T` and wraps it once — in every caller context: var-decl, function
|
||||
// argument, return value, a nested `?T` struct field, reassignment, and an
|
||||
// array element. Previously this filled the optional's {payload, has_value}
|
||||
// layout directly, corrupting the value (a multi-field payload's first field
|
||||
// was clobbered by the has_value flag, or a `?T` arg silently read as null) or
|
||||
// failing LLVM verification on a double wrap.
|
||||
// Regression (issue 0160).
|
||||
#import "modules/std.sx";
|
||||
|
||||
T :: struct { a: i64 = 0; b: i64 = 0; }
|
||||
Outer :: struct { opt: ?T = null; tag: i64 = 0; }
|
||||
|
||||
take :: (o: ?T) -> i64 { if o != null { return o!.a * 10 + o!.b; } return -1; }
|
||||
make :: () -> ?T { return .{ a = 5, b = 6 }; }
|
||||
|
||||
main :: () -> i64 {
|
||||
// var-decl
|
||||
v : ?T = .{ a = 3, b = 9 };
|
||||
if v != null { print("var: {} {}\n", v!.a, v!.b); } // 3 9
|
||||
|
||||
// function argument
|
||||
print("arg: {}\n", take(.{ a = 4, b = 7 })); // 47
|
||||
|
||||
// return value
|
||||
r := make();
|
||||
if r != null { print("ret: {} {}\n", r!.a, r!.b); } // 5 6
|
||||
|
||||
// nested ?T struct field
|
||||
o : Outer = .{ opt = .{ a = 1, b = 2 }, tag = 8 };
|
||||
if o.opt != null { print("nested: {} {} {}\n", o.opt!.a, o.opt!.b, o.tag); } // 1 2 8
|
||||
|
||||
// reassignment
|
||||
v = .{ a = 11, b = 12 };
|
||||
if v != null { print("reassign: {} {}\n", v!.a, v!.b); } // 11 12
|
||||
|
||||
// array element
|
||||
arr : [2]?T = .[ .{ a = 20 }, .{ a = 21 } ];
|
||||
if arr[0] != null { if arr[1] != null { print("arr: {} {}\n", arr[0]!.a, arr[1]!.a); } } // 20 21
|
||||
return 0;
|
||||
}
|
||||
39
examples/optionals/0907-optionals-accessor-through-chain.sx
Normal file
39
examples/optionals/0907-optionals-accessor-through-chain.sx
Normal file
@@ -0,0 +1,39 @@
|
||||
// A `#get` property accessor is reachable through an optional chain
|
||||
// (`obj?.getter`): the some-branch dispatches the getter and the result is
|
||||
// re-wrapped as `?R`; a null receiver short-circuits to null. Works for a value
|
||||
// optional (`?T`), a pointer optional (`?*T`), and a generic-instance getter
|
||||
// (`List.len`), and types correctly without an explicit annotation. Real fields
|
||||
// through `?.` keep working unchanged.
|
||||
// Regression (issue 0160).
|
||||
#import "modules/std.sx";
|
||||
|
||||
Temp :: struct {
|
||||
raw: i64 = 0;
|
||||
doubled :: (self: *Temp) -> i64 #get => self.raw * 2;
|
||||
}
|
||||
|
||||
main :: () -> i64 {
|
||||
t : Temp = .{ raw = 4 };
|
||||
|
||||
// value optional ?T — getter through chain
|
||||
ot : ?Temp = t;
|
||||
print("?T getter: {}\n", ot?.doubled ?? -1); // 8
|
||||
|
||||
// pointer optional ?*T — getter through chain
|
||||
pt : ?*Temp = @t;
|
||||
print("?*T getter: {}\n", pt?.doubled ?? -1); // 8
|
||||
|
||||
// null receiver short-circuits
|
||||
nope : ?Temp = null;
|
||||
print("null getter: {}\n", nope?.doubled ?? -1); // -1
|
||||
|
||||
// real field through chain still works
|
||||
print("?T field: {}\n", ot?.raw ?? -1); // 4
|
||||
|
||||
// generic-instance getter (List.len) through chain
|
||||
xs : List(i64) = .{};
|
||||
xs.append(10); xs.append(20); xs.append(30);
|
||||
pxs : ?*List(i64) = @xs;
|
||||
print("?*List len: {}\n", pxs?.len ?? -1); // 3
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
var: 3 9
|
||||
arg: 47
|
||||
ret: 5 6
|
||||
nested: 1 2 8
|
||||
reassign: 11 12
|
||||
arr: 20 21
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
?T getter: 8
|
||||
?*T getter: 8
|
||||
null getter: -1
|
||||
?T field: 4
|
||||
?*List len: 3
|
||||
Reference in New Issue
Block a user