fix(types): reject unknown type names instead of silent empty struct (issue 0064)
An identifier used in a type position that resolved to nothing fell through
to `type_bridge.resolveTypeName`'s empty-struct-stub fallback, silently
interning a 0-field struct named after the identifier. A value parameter
mistakenly used as a type (`(T: Type, ...) -> T`, missing the `$`) or a
typo'd type name therefore compiled and ran, rendering as `T{}`.
New post-scan diagnostic pass `checkUnknownTypeNames` (lower.zig Pass 1f)
walks every main-file function signature and non-generic struct field type
and rejects any leaf name that is not a primitive, an in-scope generic param
(`$T` / `type_params`), a declared type, or a real (non-stub) registered
type. The load-bearing empty-struct stub is left intact — forward references
and foreign-class opaque types still depend on it during the scan — and the
pass runs before body lowering, so `hasErrors()` halts the build before any
stub reaches codegen.
A value param used as a type gets a tailored hint to write `$T: Type`; a
genuine unknown gets "unknown type 'X'". Imported concrete types are
recognized via the type table, and inline compound spellings (`[:0]u8`),
arbitrary-width ints (`u1`/`u2`), and `$`-introduced generics (`-> $R`) are
exempted to avoid false positives.
Regressions: examples/1111 (tailored hint) + 1112 (typo'd field type).
This commit is contained in:
10
examples/1111-diagnostics-nondollar-type-param-rejected.sx
Normal file
10
examples/1111-diagnostics-nondollar-type-param-rejected.sx
Normal file
@@ -0,0 +1,10 @@
|
||||
// A value parameter declared `T: Type` (WITHOUT the `$` generic sigil) used in
|
||||
// a type position is rejected with a hint to write `$T: Type`. Previously the
|
||||
// type resolver silently fabricated a 0-field struct named `T`, so the call
|
||||
// compiled and rendered as `T{}` at runtime with no diagnostic.
|
||||
// Regression (issue 0064). Expected: one error per `T` use site; exit 1.
|
||||
idwrap :: (T: Type, f: Closure() -> T) -> T { return f(); }
|
||||
|
||||
main :: () -> s32 {
|
||||
return idwrap(s32, closure(() -> s32 { return 7; }));
|
||||
}
|
||||
13
examples/1112-diagnostics-unknown-type-name-rejected.sx
Normal file
13
examples/1112-diagnostics-unknown-type-name-rejected.sx
Normal file
@@ -0,0 +1,13 @@
|
||||
// An identifier used in a type position that names no declared type, builtin,
|
||||
// or in-scope generic parameter is rejected. Previously the type resolver's
|
||||
// empty-struct-stub fallback silently interned a 0-field struct under the typo,
|
||||
// so the program compiled and ran. Regression (issue 0064, broader fix).
|
||||
// Expected: a clean "unknown type" error at the field; exit 1.
|
||||
Point :: struct {
|
||||
x: s32;
|
||||
y: Coordnate; // typo for a non-existent type
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,11 @@
|
||||
error: 'T' is a value parameter, not a type; introduce a generic type parameter with `$T: Type`
|
||||
--> /Users/agra/projects/sx/examples/1111-diagnostics-nondollar-type-param-rejected.sx:6:37
|
||||
|
|
||||
6 | idwrap :: (T: Type, f: Closure() -> T) -> T { return f(); }
|
||||
| ^
|
||||
|
||||
error: 'T' is a value parameter, not a type; introduce a generic type parameter with `$T: Type`
|
||||
--> /Users/agra/projects/sx/examples/1111-diagnostics-nondollar-type-param-rejected.sx:6:43
|
||||
|
|
||||
6 | idwrap :: (T: Type, f: Closure() -> T) -> T { return f(); }
|
||||
| ^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,5 @@
|
||||
error: unknown type 'Coordnate'
|
||||
--> /Users/agra/projects/sx/examples/1112-diagnostics-unknown-type-name-rejected.sx:8:8
|
||||
|
|
||||
8 | y: Coordnate; // typo for a non-existent type
|
||||
| ^^^^^^^^^
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Reference in New Issue
Block a user