io interface (first pass)

This commit is contained in:
2026-07-13 12:02:06 +02:00
parent 2ed333c70d
commit 288df082e2
11 changed files with 589 additions and 20 deletions
+113 -8
View File
@@ -183,6 +183,8 @@ Checker :: struct {
// build_globals, so the 1:1 module.globals <-> ast.globals index identity holds.
anon_globals: [dynamic]hir.Global,
main_symbol: symbol.Id,
io_main: bool,
io_provider_template: ast.Function_Id,
sink_symbol: symbol.Id,
type_symbol: symbol.Id,
current_result: types.Type,
@@ -894,6 +896,66 @@ find_template :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(
return find_function_symbol(checker.function_index, pkg, name, file)
}
configure_io_main :: proc(checker: ^Checker) {
main_template := find_template(checker, checker.main_symbol, 0)
if main_template == ast.INVALID_FUNCTION {
return
}
main := checker.ast_module.functions[main_template]
if len(main.params) != 1 || main.params[0].comptime_value {
return
}
parameter_type := types.resolve_alias(
type_from_syntax(checker, main.params[0].type, main.pkg, main.file),
&checker.module.types,
)
io_name := symbol.intern(checker.symbols, "Io")
io_package := ast.INVALID_PACKAGE
io_type := types.INVALID
for import_item in checker.ast_module.imports {
if import_item.valid && import_item.path == "@std/io" {
candidate := types.find_named(&checker.module.types, u32(import_item.target), u32(io_name))
if types.equal(parameter_type, candidate) {
io_package = import_item.target
io_type = candidate
break
}
}
}
if io_package == ast.INVALID_PACKAGE {
return
}
provider_name := symbol.intern(checker.symbols, "_system")
provider := ast.INVALID_FUNCTION
for function, function_id in checker.ast_module.functions {
if function.pkg != io_package || function.name != provider_name {
continue
}
result := types.resolve_alias(
type_from_syntax(checker, function.result, function.pkg, function.file),
&checker.module.types,
)
if function.has_body && !function.c_abi && len(function.params) == 0 &&
!types.is_valid(function.error) && types.equal(result, io_type) {
provider = ast.function_id(function_id)
break
}
}
if provider == ast.INVALID_FUNCTION {
checker.template_diagnostics[main_template] = source.add(
checker.diagnostics,
main.span,
"@std/io does not provide the required '_system func() Io' startup implementation",
)
return
}
checker.io_main = true
checker.io_provider_template = provider
}
find_global :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(0), file := ast.INVALID_FILE) -> ast.Global_Id {
return find_global_symbol(checker.global_index, pkg, name, file)
}
@@ -2095,6 +2157,18 @@ validate_external_globals :: proc(checker: ^Checker) {
}
}
runtime_write_declaration_matches :: proc(checker: ^Checker, function: ast.Function) -> bool {
if function.variadic || len(function.params) != 3 || types.is_valid(function.error) {
return false
}
store := &checker.module.types
buffer := types.optional(store, types.pointer(store, types.ANYOPAQUE, false, true))
return type_from_syntax(checker, function.params[0].type, function.pkg, function.file) == types.C_INT &&
type_from_syntax(checker, function.params[1].type, function.pkg, function.file) == buffer &&
type_from_syntax(checker, function.params[2].type, function.pkg, function.file) == types.C_ULONG &&
type_from_syntax(checker, function.result, function.pkg, function.file) == types.C_LONG
}
validate_declarations :: proc(checker: ^Checker) {
for function, function_id in checker.ast_module.functions {
if len(function.unsupported_reason) > 0 {
@@ -2261,6 +2335,14 @@ validate_declarations :: proc(checker: ^Checker) {
"main must have a body",
)
}
external_name := function.link_name if len(function.link_name) > 0 else symbol_text(checker, function.name)
if external_name == "write" && !runtime_write_declaration_matches(checker, function) {
checker.template_diagnostics[function_id] = source.add(
checker.diagnostics,
function.span,
"external C function 'write' conflicts with the compiler runtime declaration",
)
}
}
mark_block_imports_used(checker, function.body, function.file)
delete(locals)
@@ -4070,6 +4152,9 @@ infer_all :: proc(checker: ^Checker) {
if main_template != ast.INVALID_FUNCTION {
ensure_spec(checker, main_template, nil)
}
if checker.io_main {
ensure_spec(checker, checker.io_provider_template, nil)
}
defaults_applied := false
for {
@@ -4196,6 +4281,9 @@ prune_specs :: proc(checker: ^Checker) {
if main_template != ast.INVALID_FUNCTION {
mark_spec_demanded(checker, find_spec(checker, main_template, nil), &stack)
}
if checker.io_main {
mark_spec_demanded(checker, find_spec(checker, checker.io_provider_template, nil), &stack)
}
for global in checker.ast_module.globals {
if global.external {
continue
@@ -5192,7 +5280,11 @@ build_compound_expr :: proc(
value := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
actual := checker.module.exprs[value].type
valid_target := types.is_concrete_scalar(target) && !types.is_bool(target)
valid_actual := types.is_concrete_scalar(actual) && !types.is_bool(actual)
actual_repr := types.runtime_representation(actual, store)
actual_item, actual_item_ok := types.node(store, actual)
explicit_enum := actual_item_ok && actual_item.kind == .Enum && actual_item.explicit_backing
valid_actual := (types.is_concrete_scalar(actual) || explicit_enum) &&
types.is_concrete_scalar(actual_repr) && !types.is_bool(actual_repr)
if !valid_target || !valid_actual {
id := source.addf(
checker.diagnostics,
@@ -6536,7 +6628,7 @@ build_expr :: proc(
make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
spec := checker.specs[id]
function := checker.ast_module.functions[spec.template]
if function.pkg == 0 && function.name == checker.main_symbol {
if function.pkg == 0 && function.name == checker.main_symbol && !checker.io_main {
return fmt.aprintf("main", allocator = checker.allocator)
}
if function.generated {
@@ -9151,6 +9243,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
problematic := signature_diagnostic != source.INVALID_DIAGNOSTIC ||
checker.template_diagnostics[spec.template] != source.INVALID_DIAGNOSTIC
native_main := function.pkg == 0 && function.name == checker.main_symbol && !checker.io_main
if !function.has_body {
assert(spec.hir_id == hir.function_id(len(checker.module.functions)))
append(
@@ -9161,7 +9254,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
calling_convention = .C if function.c_abi else .Brolang,
implementation = .Declaration,
linkage = .External if function.c_abi else .Internal,
is_main = function.pkg == 0 && function.name == checker.main_symbol,
is_main = native_main,
variadic = function.variadic,
params = params[:],
result = spec.result,
@@ -9259,10 +9352,10 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
hir.Function {
name = function.name,
link_name = make_link_name(checker, id),
calling_convention = .C if function.c_abi || (function.pkg == 0 && function.name == checker.main_symbol) else .Brolang,
calling_convention = .C if function.c_abi || native_main else .Brolang,
implementation = .Definition,
linkage = .External if function.c_abi || (function.pkg == 0 && function.name == checker.main_symbol) else .Internal,
is_main = function.pkg == 0 && function.name == checker.main_symbol,
linkage = .External if function.c_abi || native_main else .Internal,
is_main = native_main,
variadic = function.variadic,
params = params[:],
result = spec.result,
@@ -9710,6 +9803,7 @@ check :: proc(
symbols = symbols,
module = hir.init_module(selected, allocator),
main_symbol = symbol.intern(symbols, "main"),
io_provider_template = ast.INVALID_FUNCTION,
sink_symbol = symbol.intern(symbols, "_"),
type_symbol = symbol.intern(symbols, "type"),
target = selected,
@@ -9823,6 +9917,7 @@ check :: proc(
validate_type_nodes(&checker)
validate_declarations(&checker)
configure_io_main(&checker)
infer_all(&checker)
validate_external_globals(&checker)
prune_specs(&checker)
@@ -9849,19 +9944,29 @@ check :: proc(
synthesize_trap_main(&checker)
} else {
template := ast_module.functions[main_template]
valid_params := len(template.params) == 0 || checker.io_main && len(template.params) == 1
if main_declarations != 1 ||
!template.has_body ||
len(template.params) != 0 ||
!valid_params ||
!(template.result == types.VOID || template.result == types.I32 || template.result == types.INT) {
id := checker.template_diagnostics[main_template]
if id == source.INVALID_DIAGNOSTIC {
id = source.add(
diagnostics,
template.span,
"main must be unique, have a body, take no parameters, and return void, i32, or int",
"main must be unique, have a body, take no parameters or one @std/io Io, and return void, i32, or int",
)
}
checker.module.injected_main = hir.INVALID_FUNCTION
checker.module.io_provider = hir.INVALID_FUNCTION
replace_main_with_trap(&checker, id)
} else if checker.io_main {
main_spec := find_spec(&checker, main_template, nil)
provider_spec := find_spec(&checker, checker.io_provider_template, nil)
if main_spec != INVALID_SPEC && provider_spec != INVALID_SPEC {
checker.module.injected_main = checker.specs[main_spec].hir_id
checker.module.io_provider = checker.specs[provider_spec].hir_id
}
}
}