diff --git a/src/sema.zig b/src/sema.zig index 0a1c593..c8141c1 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -79,6 +79,9 @@ pub const Analyzer = struct { member_refs: std.ArrayList(MemberRef), /// Source text — lets `spanOf` map an AST string slice back to a Span. source: []const u8 = "", + /// True while analysing a `callconv(.c)` function body — the C ABI carries + /// no implicit `context` parameter, so `context` is unavailable there. + in_c_conv: bool = false, diagnostics: std.ArrayList(Diagnostic), scope_depth: u32, /// Stack of symbol counts at each scope entry, for popScope cleanup. @@ -703,10 +706,13 @@ pub const Analyzer = struct { fn analyzeTopLevelDecl(self: *Analyzer, node: *Node) !void { switch (node.data) { .fn_decl => |fd| { + const saved_cc = self.in_c_conv; + self.in_c_conv = fd.call_conv == .c; try self.pushScope(); try self.analyzeParams(fd.params); try self.analyzeNode(fd.body); self.popScope(); + self.in_c_conv = saved_cc; }, .const_decl => |cd| { try self.analyzeNode(cd.value); @@ -722,10 +728,13 @@ pub const Analyzer = struct { for (sd.methods) |mnode| { if (mnode.data == .fn_decl) { const m = mnode.data.fn_decl; + const saved_cc = self.in_c_conv; + self.in_c_conv = m.call_conv == .c; try self.pushScope(); try self.analyzeParams(m.params); try self.analyzeNode(m.body); self.popScope(); + self.in_c_conv = saved_cc; } } }, @@ -843,6 +852,14 @@ pub const Analyzer = struct { } fn resolveIdentifier(self: *Analyzer, name: []const u8, span: Span) !void { + if (self.in_c_conv and std.mem.eql(u8, name, "context")) { + try self.diagnostics.append(self.allocator, .{ + .level = .warn, + .span = span, + .message = "`context` is unavailable in a `callconv(.c)` function — the C ABI has no implicit context parameter; pass what you need explicitly", + }); + return; + } // Use symbol index for O(1) name lookup, then walk backwards through indices if (self.symbol_index.get(name)) |indices| { var j = indices.items.len; @@ -891,10 +908,13 @@ pub const Analyzer = struct { .return_type = local_ret_ty orelse .void_type, }); } + const saved_cc = self.in_c_conv; + self.in_c_conv = fd.call_conv == .c; try self.pushScope(); try self.analyzeParams(fd.params); try self.analyzeNode(fd.body); self.popScope(); + self.in_c_conv = saved_cc; }, .block => |blk| { try self.pushScope(); @@ -2038,6 +2058,32 @@ test "sema: member references record fields, methods, and enum variants" { try std.testing.expect(red_use); } +test "sema: context in a callconv(.c) function reports a specific diagnostic" { + const parser_mod = @import("parser.zig"); + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const source = + "Context :: struct { allocator: s64; data: s64; }" ++ + "cb :: () -> s64 callconv(.c) { context; 0; }" ++ + "ok :: () -> s64 { context; 0; }"; + var parser = parser_mod.Parser.init(alloc, source); + const root = try parser.parse(); + var an = Analyzer.init(alloc); + an.source = source; + const res = try an.analyze(root); + + var c_conv_diag = false; + var undefined_diag = false; + for (res.diagnostics) |d| { + if (std.mem.indexOf(u8, d.message, "callconv(.c)") != null) c_conv_diag = true; + if (std.mem.indexOf(u8, d.message, "undefined") != null) undefined_diag = true; + } + try std.testing.expect(c_conv_diag); // `cb` accesses context under the C ABI + try std.testing.expect(!undefined_diag); // `ok`'s context resolves cleanly +} + test "sema: variable shadowing in same scope is allowed" { const parser_mod = @import("parser.zig");