fix: literal element typing — typed-array null element, tuple coercion, positional var element (0173-0175)
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).
This commit is contained in:
@@ -1,5 +1,18 @@
|
||||
# 0173 — `(T).[ ... ]` typed array-literal with a `null` element panics (unresolved type at LLVM emission)
|
||||
|
||||
> **RESOLVED.** `resolveArrayLiteralType` had no arm for an `array_type_expr` /
|
||||
> `slice_type_expr` head, so a `([2]?i64).[...]` head fell to `else =>
|
||||
> .unresolved` — the `?i64` element type was lost and a bare `null` element
|
||||
> reached LLVM as `const_null(.unresolved)`. Fix (`src/ir/lower/expr.zig`): route
|
||||
> structural heads through `resolveTypeWithBindings` (recurses into the element).
|
||||
> To honor the no-silent-fallback rule, an UNDEFINED element name in the head is
|
||||
> now validated by `UnknownTypeChecker` (`src/ir/semantic_diagnostics.zig` —
|
||||
> wired `al.type_expr` into `walkBodyTypes`), emitting `unknown type '<name>'`
|
||||
> instead of a silent empty-struct stub. Regression:
|
||||
> `examples/optionals/0914-optionals-typed-array-literal-null-element.sx` +
|
||||
> `examples/diagnostics/1196-diagnostics-array-literal-head-unknown-type.sx`.
|
||||
> Verified by 4 adversarial reviews.
|
||||
|
||||
## Symptom
|
||||
|
||||
An explicit-type array literal of the `(T).[ elems ]` form (the `.[...]`
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# 0174 — positional literal for a TUPLE target does not coerce elements (same corruption class as 0168)
|
||||
|
||||
> **RESOLVED.** `lowerStructLiteral`'s positional branch coerced struct fields
|
||||
> and array/vector elements but not TUPLE targets (a tuple is neither — empty
|
||||
> `struct_fields`, `.unresolved` `array_elem_ty`), so a bare element was stored
|
||||
> raw into the field slot (a `{T,i1}` optional read back absent). Fix
|
||||
> (`src/ir/lower/expr.zig`): compute `tuple_fields` from `TupleInfo.fields` and
|
||||
> fold it into a unified `elem_target` (`struct_fields[i].ty` → `tuple_fields[i]`
|
||||
> → `array_elem_ty`) that steers per-element `target_type` and drives
|
||||
> `coerceToType`. Verified across optional/int→float/protocol/slice/enum/nested
|
||||
> tuple elements + named tuples by 4 adversarial reviews. Regression:
|
||||
> `examples/types/0199-types-tuple-positional-optional-element.sx`.
|
||||
|
||||
## Symptom
|
||||
|
||||
A positional literal `.{ a, b }` whose target is a TUPLE does not coerce its
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# 0175 — positional struct literal with a VARIABLE element silently zeroes the field
|
||||
|
||||
> **RESOLVED.** Root cause was named-vs-positional misclassification: the parser
|
||||
> PUNS a bare-ident element `.{ x, … }` into a named field `x = x` (the legit
|
||||
> `Vec4.{ w, z }` shorthand), so a positional-with-variable literal arrived as a
|
||||
> spurious "named" literal and the named branch left every field at its default.
|
||||
> Fix (`src/ir/lower/expr.zig`): `has_names` now consults the struct definition —
|
||||
> a punned bare-ident whose name matches no declared field reclassifies the whole
|
||||
> literal as positional; positional field coercion now uses the lowered value's
|
||||
> actual `getRefType` (not a re-inferred `src_ty`) and steers per-field
|
||||
> `target_type`. Legit shorthand, named, mixed, generic, forward-ref, and nested
|
||||
> cases all verified unbroken by 4 adversarial reviews. Regression:
|
||||
> `examples/types/0200-types-positional-struct-literal-variable-element.sx`.
|
||||
|
||||
## Symptom
|
||||
|
||||
A positional struct literal `S.{ x, ... }` whose element is a VARIABLE reference
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# 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
|
||||
|
||||
```sx
|
||||
#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.
|
||||
Reference in New Issue
Block a user