This commit is contained in:
agra
2026-03-06 10:46:28 +02:00
parent f9dda972d2
commit 69934592d8
8 changed files with 113 additions and 14 deletions

View File

@@ -411,6 +411,7 @@ pub const Function = struct {
is_comptime: bool = false,
linkage: Linkage = .internal,
call_conv: CallingConvention = .default,
source_file: ?[]const u8 = null,
pub const Param = struct {
name: StringId,

View File

@@ -82,6 +82,8 @@ pub const Lowering = struct {
lowered_functions: std.StringHashMap(void), // tracks which functions have been fully lowered
local_fn_counter: u32 = 0, // unique counter for mangling local function names
import_flags: std.StringHashMap(bool), // tracks whether each function is imported
module_scopes: ?*std.StringHashMap(std.StringHashMap(void)) = null, // per-module visible names (from import resolution)
current_source_file: ?[]const u8 = null, // source file of function currently being lowered
type_bindings: ?std.StringHashMap(TypeId) = null, // generic type param bindings ($T → concrete TypeId)
current_match_tags: ?[]const u64 = null, // type tags for current match arm (for runtime dispatch)
force_block_value: bool = false, // set by lowerBlockValue to extract if-else values
@@ -254,6 +256,7 @@ pub const Lowering = struct {
/// This preserves the old behavior for comptime evaluation contexts.
pub fn lowerDecls(self: *Lowering, decls: []const *const Node) void {
for (decls) |decl| {
self.current_source_file = decl.source_file;
const is_imported = if (self.main_file) |mf|
(if (decl.source_file) |sf| !std.mem.eql(u8, sf, mf) else false)
else
@@ -308,6 +311,7 @@ pub const Lowering = struct {
/// Pass 1: Scan declarations — register ASTs and extern stubs, but don't lower bodies.
fn scanDecls(self: *Lowering, decls: []const *const Node) void {
for (decls) |decl| {
self.current_source_file = decl.source_file;
const is_imported = if (self.main_file) |mf|
(if (decl.source_file) |sf| !std.mem.eql(u8, sf, mf) else false)
else
@@ -548,7 +552,9 @@ pub const Lowering = struct {
if (fe.c_name) |c_name| {
const c_name_id = self.module.types.internString(c_name);
const fid = self.builder.declareExtern(c_name_id, params.items, ret_ty);
self.module.getFunctionMut(fid).call_conv = cc;
const func = self.module.getFunctionMut(fid);
func.call_conv = cc;
func.source_file = self.current_source_file;
self.foreign_name_map.put(name, c_name) catch {};
return;
}
@@ -556,7 +562,23 @@ pub const Lowering = struct {
const name_id = self.module.types.internString(name);
const fid = self.builder.declareExtern(name_id, params.items, ret_ty);
self.module.getFunctionMut(fid).call_conv = cc;
const func = self.module.getFunctionMut(fid);
func.call_conv = cc;
func.source_file = self.current_source_file;
}
/// Check if a C-imported function is visible from the current source file.
/// Returns true for non-C functions (always visible) or if no scoping info available.
fn isCImportVisible(self: *Lowering, fn_name: []const u8) bool {
const fd = self.fn_ast_map.get(fn_name) orelse return true;
// Only restrict C import fn_decls: foreign_expr with no library_ref
if (fd.body.data != .foreign_expr) return true;
if (fd.body.data.foreign_expr.library_ref != null) return true;
// It's a C import fn_decl — check module scope
const scopes = self.module_scopes orelse return true;
const source = self.current_source_file orelse return true;
const scope = scopes.get(source) orelse return true;
return scope.contains(fn_name);
}
/// Lazily lower a function body on demand. Called when lowerCall can't find
@@ -590,6 +612,7 @@ pub const Lowering = struct {
const saved_defer_base = self.func_defer_base;
const saved_block_terminated = self.block_terminated;
const saved_force_block_value = self.force_block_value;
const saved_source_file = self.current_source_file;
self.func_defer_base = self.defer_stack.items.len;
self.block_terminated = false;
self.force_block_value = false;
@@ -611,6 +634,7 @@ pub const Lowering = struct {
// Function not yet declared — create it fresh via lowerFunction
self.lowerFunction(fd, name, false);
// Restore builder state
self.current_source_file = saved_source_file;
self.scope = saved_scope;
self.func_defer_base = saved_defer_base;
self.force_block_value = saved_force_block_value;
@@ -624,8 +648,10 @@ pub const Lowering = struct {
// Re-use the existing function slot — switch builder to it
self.builder.func = fid;
const func = &self.module.functions.items[@intFromEnum(fid)];
self.current_source_file = func.source_file;
if (!func.is_extern) {
// Already promoted (e.g., via lowerComptimeDeps) — skip
self.current_source_file = saved_source_file;
self.scope = saved_scope;
self.func_defer_base = saved_defer_base;
self.block_terminated = saved_block_terminated;
@@ -694,6 +720,7 @@ pub const Lowering = struct {
}
// Restore builder state
self.current_source_file = saved_source_file;
self.scope = saved_scope;
self.func_defer_base = saved_defer_base;
self.block_terminated = saved_block_terminated;
@@ -3596,6 +3623,12 @@ pub const Lowering = struct {
}
break :blk scoped;
};
// C-import visibility: deny calls to C fn_decls not in the caller's module scope
if (!self.isCImportVisible(eff_name)) {
if (self.diagnostics) |d|
d.addFmt(.err, c.callee.span, "C function '{s}' not visible; add #import for the module that declares it", .{eff_name});
return Ref.none;
}
if (self.fn_ast_map.get(eff_name)) |fd| {
if (self.current_match_tags) |tags| {
if (tags.len > 0 and self.hasCastWithRuntimeType(c)) {