lsp/sema: clearer message for 'context' inside a callconv(.c) function
Now that 'context' resolves as an implicit global, accessing it inside a callconv(.c) function (an FFI callback/trampoline) would silently resolve — but the C ABI carries no implicit context parameter, so it's actually unavailable there. Sema now tracks the current function's calling convention and, for 'context' under callconv(.c), emits a specific diagnostic ('unavailable in a callconv(.c) function — pass what you need explicitly') instead of resolving it or saying 'undefined variable'.
This commit is contained in:
46
src/sema.zig
46
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");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user