0173: resolveArrayLiteralType gained no arm for [N]T/[]T heads, so a
([2]?i64).[...] head lost its ?i64 element type and a bare null reached
LLVM as const_null(.unresolved). Route structural heads through
resolveTypeWithBindings; validate an undefined element name in the head
via UnknownTypeChecker (semantic_diagnostics.zig) instead of a silent
empty-struct stub (no-silent-fallback).
0174: positional .{...} against a TUPLE target now coerces each element
to TupleInfo.fields[i] (was neither struct nor array, so uncoerced).
0175: a positional struct literal with a bare-variable element was
misclassified as a named shorthand (parser puns .{x} -> x=x), zeroing
the fields. has_names now consults the struct definition to reclassify a
punned non-field name as positional; positional coercion uses the
lowered value's real getRefType.
Regressions: optionals/0914, types/0199, types/0200, diagnostics/1196.
Verified by 4 adversarial reviews; suite 784/0. Filed adjacent bug 0176
(protocol-typed struct field method call aborts).
2.3 KiB
0176 — calling a method through a protocol-typed struct field aborts (exit 133, no diagnostic)
Symptom
A struct field whose type is a PROTOCOL holds an erased value fine, but calling a method THROUGH that field aborts the process (exit 133, SIGABRT) with no diagnostic. Reading a non-protocol sibling field is fine; constructing the struct is fine. The crash needs the method call-through. Reproduces with BOTH struct-literal init and field assignment, so it is not a struct-literal bug — the protocol field's method dispatch / vtable through a struct slot is the suspect. Pre-existing (reproduces on clean master).
Reproduction
#import "modules/std.sx";
Speaker :: protocol { speak :: (self: *Self) -> i64; }
Dog :: struct { n: i64 = 0; }
speak :: (self: *Dog) -> i64 { return self.n; }
Holder :: struct { s: Speaker; b: i64 = 0; }
main :: () {
d := Dog.{ n = 42 };
h : Holder = .{ s = d, b = 5 }; // or: h.s = d (field assign) — same crash
print("{}\n", h.s.speak()); // <-- aborts here, exit 133, no output
}
Expected: 42. Observed: silent abort, exit 133. Reading h.b (the non-protocol
field) prints 5 fine; the crash is specifically the call through h.s.
Investigation prompt
The erased protocol value stored in a struct field appears to lose its
method-table / self pointer, so dispatch through h.s.speak() reads a
null/garbage vtable. Compare against a protocol value in a LOCAL variable
(s : Speaker = d; s.speak() — does THAT work?) to isolate whether the bug is in
storing the erased value into a struct field, or in dispatching through a field
access. Suspect the protocol fat-value {vtable/typeinfo, data-ptr} layout when
embedded as a struct field: the field store (emitStructInit / field assign) may
truncate or mis-place the fat value, or the method-dispatch lowering for
field.method() may not load the full protocol header. Look at how a protocol
local dispatches vs how a protocol struct-field dispatches
(src/ir/lower/expr.zig method-call / field-access lowering + src/backend/llvm
protocol dispatch). Follow the no-silent-fallback rule. Verify: the repro prints
42; both struct-literal and field-assign init; a protocol field reassigned to a
different concrete type dispatches correctly. Add a
examples/protocols/04xx-protocol-struct-field-dispatch.sx regression.