fix(types): extend unknown-type check into function bodies (issue 0064)
The signature/field check missed body-level type positions: a local annotation naming a non-existent type flowed through the empty-struct stub untouched, so `v: Coordnate = 5` silently compiled and ran (the value dropped) — an invalid program accepted with no diagnostic. `checkUnknownTypeNames` now also walks each main-file function body (`checkBodyTypes`): local var/const type annotations — including inside if / loop / match / push / defer / onfail blocks and decl-value blocks — are validated with the enclosing function's generic params in scope, and body-local `T :: struct/enum/union` declarations are collected first (`collectBodyDeclNames`) so legitimate locals aren't false-flagged. Nested function/closure bodies are their own scope and are not descended (safe under-coverage); explicit `cast(T)` already surfaces its own `unresolved` diagnostic and is left to it. Regression: examples/1113 (local annotation of a non-existent type, exit 1).
This commit is contained in:
@@ -564,8 +564,12 @@ pub const Lowering = struct {
|
||||
fn collectDeclaredTypeNames(self: *Lowering, decls: []const *const Node, out: *std.StringHashMap(void)) void {
|
||||
for (decls) |decl| {
|
||||
switch (decl.data) {
|
||||
.const_decl => |cd| out.put(cd.name, {}) catch {},
|
||||
.const_decl => |cd| {
|
||||
out.put(cd.name, {}) catch {};
|
||||
if (cd.value.data == .fn_decl) self.collectBodyDeclNames(cd.value.data.fn_decl.body, out);
|
||||
},
|
||||
.struct_decl => |sd| out.put(sd.name, {}) catch {},
|
||||
.fn_decl => |fd| self.collectBodyDeclNames(fd.body, out),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
@@ -581,6 +585,77 @@ pub const Lowering = struct {
|
||||
while (it_al.next()) |k| out.put(k.*, {}) catch {};
|
||||
}
|
||||
|
||||
/// Collect names declared inside a function body — local `T :: struct/enum/
|
||||
/// union` types and named consts — so a body-level type annotation
|
||||
/// referencing one isn't flagged. Recurses control-flow bodies but stops at
|
||||
/// nested function / closure boundaries (those have their own scope and are
|
||||
/// not body-checked).
|
||||
fn collectBodyDeclNames(self: *Lowering, node: *const Node, out: *std.StringHashMap(void)) void {
|
||||
switch (node.data) {
|
||||
.block => |b| for (b.stmts) |s| self.collectBodyDeclNames(s, out),
|
||||
.if_expr => |ie| {
|
||||
self.collectBodyDeclNames(ie.then_branch, out);
|
||||
if (ie.else_branch) |e| self.collectBodyDeclNames(e, out);
|
||||
},
|
||||
.while_expr => |we| self.collectBodyDeclNames(we.body, out),
|
||||
.for_expr => |fe| self.collectBodyDeclNames(fe.body, out),
|
||||
.match_expr => |me| for (me.arms) |arm| self.collectBodyDeclNames(arm.body, out),
|
||||
.push_stmt => |ps| self.collectBodyDeclNames(ps.body, out),
|
||||
.defer_stmt => |ds| self.collectBodyDeclNames(ds.expr, out),
|
||||
.onfail_stmt => |os| self.collectBodyDeclNames(os.body, out),
|
||||
.const_decl => |cd| {
|
||||
out.put(cd.name, {}) catch {};
|
||||
self.collectBodyDeclNames(cd.value, out);
|
||||
},
|
||||
.var_decl => |vd| if (vd.value) |v| self.collectBodyDeclNames(v, out),
|
||||
.struct_decl => |sd| out.put(sd.name, {}) catch {},
|
||||
.enum_decl => |ed| out.put(ed.name, {}) catch {},
|
||||
.union_decl => |ud| out.put(ud.name, {}) catch {},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Walk a function body checking type annotations on local var / const
|
||||
/// declarations (and body-local struct fields). `in_scope` and `type_vals`
|
||||
/// carry the enclosing function's generic params / value-`Type` params.
|
||||
/// Recurses control-flow and decl-value blocks (so `x := if c { a: T; a }`
|
||||
/// is reached) but not nested function / closure bodies.
|
||||
fn checkBodyTypes(
|
||||
self: *Lowering,
|
||||
node: *const Node,
|
||||
declared: *std.StringHashMap(void),
|
||||
in_scope: []const ast.StructTypeParam,
|
||||
type_vals: []const []const u8,
|
||||
) void {
|
||||
switch (node.data) {
|
||||
.block => |b| for (b.stmts) |s| self.checkBodyTypes(s, declared, in_scope, type_vals),
|
||||
.if_expr => |ie| {
|
||||
self.checkBodyTypes(ie.then_branch, declared, in_scope, type_vals);
|
||||
if (ie.else_branch) |e| self.checkBodyTypes(e, declared, in_scope, type_vals);
|
||||
},
|
||||
.while_expr => |we| self.checkBodyTypes(we.body, declared, in_scope, type_vals),
|
||||
.for_expr => |fe| self.checkBodyTypes(fe.body, declared, in_scope, type_vals),
|
||||
.match_expr => |me| for (me.arms) |arm| self.checkBodyTypes(arm.body, declared, in_scope, type_vals),
|
||||
.push_stmt => |ps| self.checkBodyTypes(ps.body, declared, in_scope, type_vals),
|
||||
.defer_stmt => |ds| self.checkBodyTypes(ds.expr, declared, in_scope, type_vals),
|
||||
.onfail_stmt => |os| self.checkBodyTypes(os.body, declared, in_scope, type_vals),
|
||||
.var_decl => |vd| {
|
||||
if (vd.type_annotation) |ta| self.checkTypeNodeForUnknown(ta, declared, in_scope, type_vals);
|
||||
if (vd.value) |v| self.checkBodyTypes(v, declared, in_scope, type_vals);
|
||||
},
|
||||
.const_decl => |cd| {
|
||||
if (cd.type_annotation) |ta| self.checkTypeNodeForUnknown(ta, declared, in_scope, type_vals);
|
||||
self.checkBodyTypes(cd.value, declared, in_scope, type_vals);
|
||||
},
|
||||
.assignment => |a| self.checkBodyTypes(a.value, declared, in_scope, type_vals),
|
||||
.return_stmt => |r| if (r.value) |v| self.checkBodyTypes(v, declared, in_scope, type_vals),
|
||||
.struct_decl => |sd| if (sd.type_params.len == 0) {
|
||||
for (sd.field_types) |ft| self.checkTypeNodeForUnknown(ft, declared, in_scope, type_vals);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn checkStructFieldTypes(self: *Lowering, sd: *const ast.StructDecl, declared: *std.StringHashMap(void)) void {
|
||||
// Generic struct fields reference the struct's own type params ($T) —
|
||||
// resolved at instantiation, not here.
|
||||
@@ -603,6 +678,7 @@ pub const Lowering = struct {
|
||||
}
|
||||
for (fd.params) |p| self.checkTypeNodeForUnknown(p.type_expr, declared, fd.type_params, type_vals.items);
|
||||
if (fd.return_type) |rt| self.checkTypeNodeForUnknown(rt, declared, fd.type_params, type_vals.items);
|
||||
self.checkBodyTypes(fd.body, declared, fd.type_params, type_vals.items);
|
||||
}
|
||||
|
||||
/// Recurse a type-annotation node to its leaf names, reporting any unknown.
|
||||
|
||||
Reference in New Issue
Block a user