This commit is contained in:
agra
2026-02-20 13:28:38 +02:00
parent 5956303366
commit 6f927361aa
9 changed files with 657 additions and 36 deletions

View File

@@ -250,6 +250,9 @@ pub const Analyzer = struct {
}
self.popScope();
},
.ufcs_alias => |ua| {
try self.addSymbol(ua.name, .function, null, node.span);
},
else => {},
}
}
@@ -505,7 +508,7 @@ pub const Analyzer = struct {
try self.analyzeNode(val);
}
},
.enum_decl, .struct_decl, .union_decl, .array_type_expr, .slice_type_expr, .array_literal, .parameterized_type_expr, .index_expr, .slice_expr, .insert_expr => {},
.enum_decl, .struct_decl, .union_decl, .array_type_expr, .slice_type_expr, .array_literal, .parameterized_type_expr, .index_expr, .slice_expr, .insert_expr, .ufcs_alias => {},
.namespace_decl => |ns| {
try self.pushScope();
for (ns.decls) |d| {
@@ -543,7 +546,8 @@ pub const Analyzer = struct {
fn addSymbol(self: *Analyzer, name: []const u8, kind: SymbolKind, ty: ?Type, span: Span) !void {
// Check for duplicate using the symbol index
if (self.symbol_index.get(name)) |indices| {
// Variables are allowed to shadow in the same scope (sx semantics)
if (kind != .variable) if (self.symbol_index.get(name)) |indices| {
const scope_start: u32 = if (self.scope_starts.items.len > 0)
self.scope_starts.items[self.scope_starts.items.len - 1]
else
@@ -561,7 +565,7 @@ pub const Analyzer = struct {
}
}
}
}
};
try self.symbols.append(self.allocator, .{
.name = name,
@@ -579,6 +583,11 @@ pub const Analyzer = struct {
try gop.value_ptr.append(self.allocator, idx);
}
/// Check if a symbol name has been registered.
pub fn hasSymbol(self: *const Analyzer, name: []const u8) bool {
return self.symbol_index.contains(name);
}
/// Pre-register an imported symbol so references in this file can resolve to it.
pub fn preRegisterSymbol(self: *Analyzer, sym: Symbol) !void {
try self.symbols.append(self.allocator, sym);
@@ -710,7 +719,17 @@ pub const Analyzer = struct {
},
.for_expr => |fe| {
try self.analyzeNode(fe.iterable);
try self.pushScope();
if (!std.mem.eql(u8, fe.capture_name, "_")) {
try self.addSymbol(fe.capture_name, .variable, null, node.span);
}
if (fe.index_name) |idx_name| {
if (!std.mem.eql(u8, idx_name, "_")) {
try self.addSymbol(idx_name, .variable, .{ .signed = 64 }, node.span);
}
}
try self.analyzeNode(fe.body);
self.popScope();
},
.spread_expr => |se| try self.analyzeNode(se.operand),
.break_expr, .continue_expr => {},
@@ -784,8 +803,12 @@ pub const Analyzer = struct {
.index_expr,
.slice_expr,
.tuple_type_expr,
.ufcs_alias,
=> {},
.ufcs_alias => |ua| {
// Register the alias name as a function and resolve the target
try self.addSymbol(ua.name, .function, null, node.span);
try self.resolveIdentifier(ua.target, node.span);
},
.tuple_literal => |tl| {
for (tl.elements) |elem| {
try self.analyzeNode(elem.value);
@@ -1329,3 +1352,88 @@ test "sema: var_decl infers struct type from parameterized call literal" {
}
try std.testing.expect(found_list);
}
test "sema: variable shadowing in same scope is allowed" {
const parser_mod = @import("parser.zig");
// Two variables with the same name in the same function body — sx allows this
const source = "main :: () { x : s64 = 1; x : f64 = 2.0; }";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
var parser = parser_mod.Parser.init(alloc, source);
const root = try parser.parse();
var analyzer = Analyzer.init(alloc);
const result = try analyzer.analyze(root);
// Should have NO diagnostics — variable shadowing is allowed
for (result.diagnostics) |d| {
if (std.mem.eql(u8, d.message, "duplicate declaration")) {
return error.TestUnexpectedResult;
}
}
}
test "sema: ufcs_alias registers symbol" {
const parser_mod = @import("parser.zig");
const source = "add :: (a: s64, b: s64) -> s64 { a + b; } main :: () { sum :: ufcs add; sum(1, 2); }";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
var parser = parser_mod.Parser.init(alloc, source);
const root = try parser.parse();
var analyzer = Analyzer.init(alloc);
const result = try analyzer.analyze(root);
// `sum` should be registered as a symbol — no "undefined variable" diagnostic
for (result.diagnostics) |d| {
if (std.mem.eql(u8, d.message, "undefined variable")) {
return error.TestUnexpectedResult;
}
}
// Should find `sum` in symbols
var found_sum = false;
for (result.symbols) |sym| {
if (std.mem.eql(u8, sym.name, "sum")) {
found_sum = true;
try std.testing.expectEqual(SymbolKind.function, sym.kind);
break;
}
}
try std.testing.expect(found_sum);
}
test "sema: top-level ufcs_alias registers symbol" {
const parser_mod = @import("parser.zig");
const source = "add :: (a: s64, b: s64) -> s64 { a + b; } sum :: ufcs add;";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
const alloc = arena.allocator();
var parser = parser_mod.Parser.init(alloc, source);
const root = try parser.parse();
var analyzer = Analyzer.init(alloc);
const result = try analyzer.analyze(root);
// No diagnostics
try std.testing.expectEqual(@as(usize, 0), result.diagnostics.len);
// Should find `sum` as function symbol
var found_sum = false;
for (result.symbols) |sym| {
if (std.mem.eql(u8, sym.name, "sum")) {
found_sum = true;
try std.testing.expectEqual(SymbolKind.function, sym.kind);
break;
}
}
try std.testing.expect(found_sum);
}