c import
This commit is contained in:
10
examples/issue-0019/c_wrapper.sx
Normal file
10
examples/issue-0019/c_wrapper.sx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// This module imports C functions and provides wrappers
|
||||||
|
#import c {
|
||||||
|
#include "vendors/test_c/test.h";
|
||||||
|
#source "vendors/test_c/test.c";
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wrapper function that calls the C function
|
||||||
|
wrapped_add :: (a: s32, b: s32) -> s32 {
|
||||||
|
add_numbers(a, b);
|
||||||
|
}
|
||||||
18
examples/issue-0019/main_bad.sx
Normal file
18
examples/issue-0019/main_bad.sx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// Test: other.sx calls C functions without importing the C module
|
||||||
|
// main imports both c_wrapper.sx and other.sx
|
||||||
|
// other.sx should NOT have access to C functions from c_wrapper
|
||||||
|
#import "../modules/std.sx";
|
||||||
|
#import "c_wrapper.sx";
|
||||||
|
#import "other.sx";
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
// This works: we import c_wrapper so we have transitive access
|
||||||
|
result := wrapped_add(10, 20);
|
||||||
|
print("wrapped_add(10, 20) = {}\n", result);
|
||||||
|
|
||||||
|
// This calls other.sx's function which tries to call add_numbers
|
||||||
|
// other.sx did NOT import c_wrapper.sx, so this should fail
|
||||||
|
bad := use_c_directly();
|
||||||
|
print("use_c_directly() = {}\n", bad);
|
||||||
|
0;
|
||||||
|
}
|
||||||
10
examples/issue-0019/main_good.sx
Normal file
10
examples/issue-0019/main_good.sx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// Test: calling wrapper functions works (we import the module)
|
||||||
|
#import "../modules/std.sx";
|
||||||
|
#import "c_wrapper.sx";
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
// This should work: calling the sx wrapper
|
||||||
|
result := wrapped_add(10, 20);
|
||||||
|
print("wrapped_add(10, 20) = {}\n", result);
|
||||||
|
0;
|
||||||
|
}
|
||||||
6
examples/issue-0019/other.sx
Normal file
6
examples/issue-0019/other.sx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// This file does NOT import c_wrapper.sx
|
||||||
|
// It should NOT be able to call add_numbers
|
||||||
|
|
||||||
|
use_c_directly :: () -> s32 {
|
||||||
|
add_numbers(5, 3);
|
||||||
|
}
|
||||||
@@ -382,21 +382,28 @@ pub fn writeCObjectFiles(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Walk the resolved AST and collect CImportInfo from all c_import_decl nodes.
|
/// Walk the resolved AST and collect CImportInfo from all c_import_decl nodes.
|
||||||
|
/// Deduplicates by source pointer identity (shared nodes from import propagation).
|
||||||
pub fn collectCImportSources(allocator: std.mem.Allocator, root: *const Node) ![]CImportInfo {
|
pub fn collectCImportSources(allocator: std.mem.Allocator, root: *const Node) ![]CImportInfo {
|
||||||
if (root.data != .root) return &.{};
|
if (root.data != .root) return &.{};
|
||||||
|
|
||||||
var infos = std.ArrayList(CImportInfo).empty;
|
var infos = std.ArrayList(CImportInfo).empty;
|
||||||
|
var seen = std.AutoHashMap([*]const []const u8, void).init(allocator);
|
||||||
|
defer seen.deinit();
|
||||||
|
|
||||||
for (root.data.root.decls) |decl| {
|
for (root.data.root.decls) |decl| {
|
||||||
switch (decl.data) {
|
switch (decl.data) {
|
||||||
.c_import_decl => |ci| {
|
.c_import_decl => |ci| {
|
||||||
if (ci.sources.len > 0) {
|
if (ci.sources.len > 0) {
|
||||||
try infos.append(allocator, .{
|
const key = ci.sources.ptr;
|
||||||
.sources = ci.sources,
|
if (!seen.contains(key)) {
|
||||||
.includes = ci.includes,
|
try seen.put(key, {});
|
||||||
.defines = ci.defines,
|
try infos.append(allocator, .{
|
||||||
.flags = ci.flags,
|
.sources = ci.sources,
|
||||||
});
|
.includes = ci.includes,
|
||||||
|
.defines = ci.defines,
|
||||||
|
.flags = ci.flags,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.namespace_decl => |ns| {
|
.namespace_decl => |ns| {
|
||||||
@@ -404,12 +411,16 @@ pub fn collectCImportSources(allocator: std.mem.Allocator, root: *const Node) ![
|
|||||||
if (nd.data == .c_import_decl) {
|
if (nd.data == .c_import_decl) {
|
||||||
const nci = nd.data.c_import_decl;
|
const nci = nd.data.c_import_decl;
|
||||||
if (nci.sources.len > 0) {
|
if (nci.sources.len > 0) {
|
||||||
try infos.append(allocator, .{
|
const key = nci.sources.ptr;
|
||||||
.sources = nci.sources,
|
if (!seen.contains(key)) {
|
||||||
.includes = nci.includes,
|
try seen.put(key, {});
|
||||||
.defines = nci.defines,
|
try infos.append(allocator, .{
|
||||||
.flags = nci.flags,
|
.sources = nci.sources,
|
||||||
});
|
.includes = nci.includes,
|
||||||
|
.defines = nci.defines,
|
||||||
|
.flags = nci.flags,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/core.zig
10
src/core.zig
@@ -23,6 +23,7 @@ pub const Compilation = struct {
|
|||||||
root: ?*Node = null,
|
root: ?*Node = null,
|
||||||
resolved_root: ?*Node = null,
|
resolved_root: ?*Node = null,
|
||||||
import_sources: std.StringHashMap([:0]const u8),
|
import_sources: std.StringHashMap([:0]const u8),
|
||||||
|
module_scopes: std.StringHashMap(std.StringHashMap(void)),
|
||||||
sema_result: ?sema.SemaResult = null,
|
sema_result: ?sema.SemaResult = null,
|
||||||
ir_emitter: ?ir.LLVMEmitter = null,
|
ir_emitter: ?ir.LLVMEmitter = null,
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ pub const Compilation = struct {
|
|||||||
.source = source,
|
.source = source,
|
||||||
.diagnostics = errors.DiagnosticList.init(allocator, source, file_path),
|
.diagnostics = errors.DiagnosticList.init(allocator, source, file_path),
|
||||||
.import_sources = std.StringHashMap([:0]const u8).init(allocator),
|
.import_sources = std.StringHashMap([:0]const u8).init(allocator),
|
||||||
|
.module_scopes = std.StringHashMap(std.StringHashMap(void)).init(allocator),
|
||||||
.target_config = target_config,
|
.target_config = target_config,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -66,6 +68,13 @@ pub const Compilation = struct {
|
|||||||
&self.diagnostics,
|
&self.diagnostics,
|
||||||
) catch return error.CompileError;
|
) catch return error.CompileError;
|
||||||
|
|
||||||
|
// Preserve per-module visibility scopes for C import access checking
|
||||||
|
self.module_scopes.put(self.file_path, mod.scope) catch {};
|
||||||
|
var cache_it = cache.iterator();
|
||||||
|
while (cache_it.next()) |entry| {
|
||||||
|
self.module_scopes.put(entry.key_ptr.*, entry.value_ptr.scope) catch {};
|
||||||
|
}
|
||||||
|
|
||||||
// Store main file source in import_sources so error reporting can find it
|
// Store main file source in import_sources so error reporting can find it
|
||||||
self.import_sources.put(self.file_path, self.source) catch {};
|
self.import_sources.put(self.file_path, self.source) catch {};
|
||||||
|
|
||||||
@@ -143,6 +152,7 @@ pub const Compilation = struct {
|
|||||||
lowering.resolved_root = root;
|
lowering.resolved_root = root;
|
||||||
lowering.target_config = self.target_config;
|
lowering.target_config = self.target_config;
|
||||||
lowering.diagnostics = &self.diagnostics;
|
lowering.diagnostics = &self.diagnostics;
|
||||||
|
lowering.module_scopes = &self.module_scopes;
|
||||||
lowering.lowerRoot(root);
|
lowering.lowerRoot(root);
|
||||||
if (self.diagnostics.hasErrors()) return error.CompileError;
|
if (self.diagnostics.hasErrors()) return error.CompileError;
|
||||||
return module;
|
return module;
|
||||||
|
|||||||
@@ -411,6 +411,7 @@ pub const Function = struct {
|
|||||||
is_comptime: bool = false,
|
is_comptime: bool = false,
|
||||||
linkage: Linkage = .internal,
|
linkage: Linkage = .internal,
|
||||||
call_conv: CallingConvention = .default,
|
call_conv: CallingConvention = .default,
|
||||||
|
source_file: ?[]const u8 = null,
|
||||||
|
|
||||||
pub const Param = struct {
|
pub const Param = struct {
|
||||||
name: StringId,
|
name: StringId,
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ pub const Lowering = struct {
|
|||||||
lowered_functions: std.StringHashMap(void), // tracks which functions have been fully lowered
|
lowered_functions: std.StringHashMap(void), // tracks which functions have been fully lowered
|
||||||
local_fn_counter: u32 = 0, // unique counter for mangling local function names
|
local_fn_counter: u32 = 0, // unique counter for mangling local function names
|
||||||
import_flags: std.StringHashMap(bool), // tracks whether each function is imported
|
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)
|
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)
|
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
|
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.
|
/// This preserves the old behavior for comptime evaluation contexts.
|
||||||
pub fn lowerDecls(self: *Lowering, decls: []const *const Node) void {
|
pub fn lowerDecls(self: *Lowering, decls: []const *const Node) void {
|
||||||
for (decls) |decl| {
|
for (decls) |decl| {
|
||||||
|
self.current_source_file = decl.source_file;
|
||||||
const is_imported = if (self.main_file) |mf|
|
const is_imported = if (self.main_file) |mf|
|
||||||
(if (decl.source_file) |sf| !std.mem.eql(u8, sf, mf) else false)
|
(if (decl.source_file) |sf| !std.mem.eql(u8, sf, mf) else false)
|
||||||
else
|
else
|
||||||
@@ -308,6 +311,7 @@ pub const Lowering = struct {
|
|||||||
/// Pass 1: Scan declarations — register ASTs and extern stubs, but don't lower bodies.
|
/// Pass 1: Scan declarations — register ASTs and extern stubs, but don't lower bodies.
|
||||||
fn scanDecls(self: *Lowering, decls: []const *const Node) void {
|
fn scanDecls(self: *Lowering, decls: []const *const Node) void {
|
||||||
for (decls) |decl| {
|
for (decls) |decl| {
|
||||||
|
self.current_source_file = decl.source_file;
|
||||||
const is_imported = if (self.main_file) |mf|
|
const is_imported = if (self.main_file) |mf|
|
||||||
(if (decl.source_file) |sf| !std.mem.eql(u8, sf, mf) else false)
|
(if (decl.source_file) |sf| !std.mem.eql(u8, sf, mf) else false)
|
||||||
else
|
else
|
||||||
@@ -548,7 +552,9 @@ pub const Lowering = struct {
|
|||||||
if (fe.c_name) |c_name| {
|
if (fe.c_name) |c_name| {
|
||||||
const c_name_id = self.module.types.internString(c_name);
|
const c_name_id = self.module.types.internString(c_name);
|
||||||
const fid = self.builder.declareExtern(c_name_id, params.items, ret_ty);
|
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 {};
|
self.foreign_name_map.put(name, c_name) catch {};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -556,7 +562,23 @@ pub const Lowering = struct {
|
|||||||
|
|
||||||
const name_id = self.module.types.internString(name);
|
const name_id = self.module.types.internString(name);
|
||||||
const fid = self.builder.declareExtern(name_id, params.items, ret_ty);
|
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
|
/// 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_defer_base = self.func_defer_base;
|
||||||
const saved_block_terminated = self.block_terminated;
|
const saved_block_terminated = self.block_terminated;
|
||||||
const saved_force_block_value = self.force_block_value;
|
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.func_defer_base = self.defer_stack.items.len;
|
||||||
self.block_terminated = false;
|
self.block_terminated = false;
|
||||||
self.force_block_value = false;
|
self.force_block_value = false;
|
||||||
@@ -611,6 +634,7 @@ pub const Lowering = struct {
|
|||||||
// Function not yet declared — create it fresh via lowerFunction
|
// Function not yet declared — create it fresh via lowerFunction
|
||||||
self.lowerFunction(fd, name, false);
|
self.lowerFunction(fd, name, false);
|
||||||
// Restore builder state
|
// Restore builder state
|
||||||
|
self.current_source_file = saved_source_file;
|
||||||
self.scope = saved_scope;
|
self.scope = saved_scope;
|
||||||
self.func_defer_base = saved_defer_base;
|
self.func_defer_base = saved_defer_base;
|
||||||
self.force_block_value = saved_force_block_value;
|
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
|
// Re-use the existing function slot — switch builder to it
|
||||||
self.builder.func = fid;
|
self.builder.func = fid;
|
||||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||||
|
self.current_source_file = func.source_file;
|
||||||
if (!func.is_extern) {
|
if (!func.is_extern) {
|
||||||
// Already promoted (e.g., via lowerComptimeDeps) — skip
|
// Already promoted (e.g., via lowerComptimeDeps) — skip
|
||||||
|
self.current_source_file = saved_source_file;
|
||||||
self.scope = saved_scope;
|
self.scope = saved_scope;
|
||||||
self.func_defer_base = saved_defer_base;
|
self.func_defer_base = saved_defer_base;
|
||||||
self.block_terminated = saved_block_terminated;
|
self.block_terminated = saved_block_terminated;
|
||||||
@@ -694,6 +720,7 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Restore builder state
|
// Restore builder state
|
||||||
|
self.current_source_file = saved_source_file;
|
||||||
self.scope = saved_scope;
|
self.scope = saved_scope;
|
||||||
self.func_defer_base = saved_defer_base;
|
self.func_defer_base = saved_defer_base;
|
||||||
self.block_terminated = saved_block_terminated;
|
self.block_terminated = saved_block_terminated;
|
||||||
@@ -3596,6 +3623,12 @@ pub const Lowering = struct {
|
|||||||
}
|
}
|
||||||
break :blk scoped;
|
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.fn_ast_map.get(eff_name)) |fd| {
|
||||||
if (self.current_match_tags) |tags| {
|
if (self.current_match_tags) |tags| {
|
||||||
if (tags.len > 0 and self.hasCastWithRuntimeType(c)) {
|
if (tags.len > 0 and self.hasCastWithRuntimeType(c)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user