fix(diag): imported generic struct field with bad type → diagnostic, not .unresolved/silent stub [stdlib E3 attempt-4]

attempt-3 closed the MAIN-file value-param-as-type quadrant (0172) in the
UnknownTypeChecker, but the checker only walks main-file decls — an IMPORTED
generic struct's field with a bad type name was never checked. Worse, the
generic-struct INSTANTIATION resolved its field type nodes in the (possibly
cross-module) instantiation site's source context, not the template's module.
So for `Bad :: struct($N: u32) { x: N; }` declared in an imported module and
used as `Bad(3)` from main, the field `x: N` resolved against the main file:
the value-param-as-type leaf poisoned it with `.unresolved` and PANICKED at
LLVM emission, and the genuinely-undeclared sibling (`y: Missing` in a generic
import, distinct from the non-generic 0759 case) silently fabricated a 0-field
stub.

Root cause + uniform fix: capture the declaring module on each StructTemplate
and resolve its field type nodes in THAT source context during
instantiateGenericStruct. The source-aware nominal leaf then classifies main vs
imported by the TEMPLATE's file, so both failure modes are diagnosed at the
right authority for every quadrant — main + imported, undeclared name + value
param used as a type:
- imported `.undeclared` field → the existing leaf emits "unknown type 'X'"
  (now reached because `from` is the template's module, not main).
- imported value-param-as-type → the `is_generic` leaf, when the name is bound
  as a comptime VALUE (`comptime_value_bindings`), emits the tailored
  "'N' is a value parameter, not a type" hint (gated to non-main; the
  UnknownTypeChecker owns the main-file case). Caught in every type position
  (`x: N`, `*N`, `[3]N`, `?N`). A genuinely-unbound type param (`$R`) stays a
  silent `.unresolved`.

No `.unresolved` reaches LLVM for these cases (hasErrors halts after lowering);
the emit_llvm `.unresolved` @panic tripwire stays as the last-resort sentinel.
Valid value-param VALUE positions (`[N]u8` dim, `Vector(N,T)` lane) and
`$T:Type`/`$T:Protocol` type-param fields still resolve.

Regressions:
- 0760-modules-imported-generic-value-param-as-field-type (panic-before / clean
  diagnostic-after).
- 0761-modules-imported-generic-undeclared-field (silent-compile-before / clean
  diagnostic-after).
0171/0172/0759 stay green; main-file quadrants emit exactly one error.

Gate: zig build; zig build test (423/423 + LSP corpus sweep); run_examples 501
passed / 0 failed (prior 499 byte-identical); m3te ios-sim build exit 0.
This commit is contained in:
agra
2026-06-08 09:37:52 +03:00
parent a0390a63ab
commit 81621703ca
12 changed files with 130 additions and 7 deletions

View File

@@ -15,6 +15,15 @@ pub const StructTemplate = struct {
type_params: []const TemplateParam,
field_names: []const []const u8,
field_type_nodes: []const *const Node, // raw AST pointers — must be copied from heap nodes
// The module that DECLARED this template. Instantiation resolves the
// field type nodes in THIS source context, not the (possibly cross-module)
// instantiation site — so a field naming a type visible only in the
// template's module resolves correctly, and the source-aware nominal leaf
// classifies main vs imported by the TEMPLATE's file (an undeclared field
// type or a value param used as a type is diagnosed at the right authority,
// never silently stubbed). Null only when the decl carried no source file
// (synthesized / comptime registration).
source_file: ?[]const u8 = null,
};
pub const TemplateParam = struct {
name: []const u8,