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),
|
member_refs: std.ArrayList(MemberRef),
|
||||||
/// Source text — lets `spanOf` map an AST string slice back to a Span.
|
/// Source text — lets `spanOf` map an AST string slice back to a Span.
|
||||||
source: []const u8 = "",
|
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),
|
diagnostics: std.ArrayList(Diagnostic),
|
||||||
scope_depth: u32,
|
scope_depth: u32,
|
||||||
/// Stack of symbol counts at each scope entry, for popScope cleanup.
|
/// 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 {
|
fn analyzeTopLevelDecl(self: *Analyzer, node: *Node) !void {
|
||||||
switch (node.data) {
|
switch (node.data) {
|
||||||
.fn_decl => |fd| {
|
.fn_decl => |fd| {
|
||||||
|
const saved_cc = self.in_c_conv;
|
||||||
|
self.in_c_conv = fd.call_conv == .c;
|
||||||
try self.pushScope();
|
try self.pushScope();
|
||||||
try self.analyzeParams(fd.params);
|
try self.analyzeParams(fd.params);
|
||||||
try self.analyzeNode(fd.body);
|
try self.analyzeNode(fd.body);
|
||||||
self.popScope();
|
self.popScope();
|
||||||
|
self.in_c_conv = saved_cc;
|
||||||
},
|
},
|
||||||
.const_decl => |cd| {
|
.const_decl => |cd| {
|
||||||
try self.analyzeNode(cd.value);
|
try self.analyzeNode(cd.value);
|
||||||
@@ -722,10 +728,13 @@ pub const Analyzer = struct {
|
|||||||
for (sd.methods) |mnode| {
|
for (sd.methods) |mnode| {
|
||||||
if (mnode.data == .fn_decl) {
|
if (mnode.data == .fn_decl) {
|
||||||
const m = 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.pushScope();
|
||||||
try self.analyzeParams(m.params);
|
try self.analyzeParams(m.params);
|
||||||
try self.analyzeNode(m.body);
|
try self.analyzeNode(m.body);
|
||||||
self.popScope();
|
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 {
|
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
|
// Use symbol index for O(1) name lookup, then walk backwards through indices
|
||||||
if (self.symbol_index.get(name)) |indices| {
|
if (self.symbol_index.get(name)) |indices| {
|
||||||
var j = indices.items.len;
|
var j = indices.items.len;
|
||||||
@@ -891,10 +908,13 @@ pub const Analyzer = struct {
|
|||||||
.return_type = local_ret_ty orelse .void_type,
|
.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.pushScope();
|
||||||
try self.analyzeParams(fd.params);
|
try self.analyzeParams(fd.params);
|
||||||
try self.analyzeNode(fd.body);
|
try self.analyzeNode(fd.body);
|
||||||
self.popScope();
|
self.popScope();
|
||||||
|
self.in_c_conv = saved_cc;
|
||||||
},
|
},
|
||||||
.block => |blk| {
|
.block => |blk| {
|
||||||
try self.pushScope();
|
try self.pushScope();
|
||||||
@@ -2038,6 +2058,32 @@ test "sema: member references record fields, methods, and enum variants" {
|
|||||||
try std.testing.expect(red_use);
|
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" {
|
test "sema: variable shadowing in same scope is allowed" {
|
||||||
const parser_mod = @import("parser.zig");
|
const parser_mod = @import("parser.zig");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user