_ prefixed top-level symbols now file-local

This commit is contained in:
2026-07-11 23:37:30 +02:00
parent 6dd6b7ff54
commit 220b1c6e82
15 changed files with 282 additions and 90 deletions
+1
View File
@@ -13,6 +13,7 @@ roadmap and milestone history.
- package-level functions, globals, native type declarations, and `Name :: alias T`
- directory packages with merged declarations
- file-local relative imports, import aliases, and qualified member access
- native top-level declarations beginning with `_` are visible only within their source file; locals, fields, parameters, and C declarations are unaffected
- relative `.h` imports as synthetic C header package namespaces
- root `main` validation with trap executable recovery for missing or unusable entry points
+2
View File
@@ -211,6 +211,7 @@ Function :: struct {
c_abi: bool,
imported: bool,
generated: bool,
file_hidden: bool,
has_body: bool,
variadic: bool,
params: []Param,
@@ -232,6 +233,7 @@ Global :: struct {
immutable: bool,
external: bool,
writable: bool,
file_hidden: bool,
expr: Expr_Id,
diagnostic: source.Diagnostic_Id,
}
+102 -56
View File
@@ -110,12 +110,16 @@ Build_Ctx :: struct {
Function_Index_Entry :: struct {
scope: ast.Package_Id,
file: ast.File_Id,
hidden: bool,
name: symbol.Id,
id: ast.Function_Id,
}
Global_Index_Entry :: struct {
scope: ast.Package_Id,
file: ast.File_Id,
hidden: bool,
name: symbol.Id,
id: ast.Global_Id,
}
@@ -617,7 +621,7 @@ import_index_less :: proc(left, right: Import_Index_Entry) -> bool {
return left.id < right.id
}
find_function_symbol :: proc(index: []Function_Index_Entry, scope: ast.Package_Id, name: symbol.Id) -> ast.Function_Id {
find_function_symbol :: proc(index: []Function_Index_Entry, scope: ast.Package_Id, name: symbol.Id, file := ast.INVALID_FILE) -> ast.Function_Id {
low := 0
high := len(index)
for low < high {
@@ -629,13 +633,22 @@ find_function_symbol :: proc(index: []Function_Index_Entry, scope: ast.Package_I
high = middle
}
}
if low < len(index) && index[low].scope == scope && index[low].name == name {
return index[low].id
visible := ast.INVALID_FUNCTION
for low < len(index) && index[low].scope == scope && index[low].name == name {
entry := index[low]
if entry.hidden {
if entry.file == file {
return entry.id
}
} else {
visible = entry.id
}
low += 1
}
return ast.INVALID_FUNCTION
return visible
}
find_global_symbol :: proc(index: []Global_Index_Entry, scope: ast.Package_Id, name: symbol.Id) -> ast.Global_Id {
find_global_symbol :: proc(index: []Global_Index_Entry, scope: ast.Package_Id, name: symbol.Id, file := ast.INVALID_FILE) -> ast.Global_Id {
low := 0
high := len(index)
for low < high {
@@ -647,10 +660,19 @@ find_global_symbol :: proc(index: []Global_Index_Entry, scope: ast.Package_Id, n
high = middle
}
}
if low < len(index) && index[low].scope == scope && index[low].name == name {
return index[low].id
visible := ast.INVALID_GLOBAL
for low < len(index) && index[low].scope == scope && index[low].name == name {
entry := index[low]
if entry.hidden {
if entry.file == file {
return entry.id
}
} else {
visible = entry.id
}
low += 1
}
return ast.INVALID_GLOBAL
return visible
}
find_import_symbol :: proc(index: []Import_Index_Entry, scope: ast.File_Id, name: symbol.Id) -> ast.Import_Id {
@@ -684,14 +706,14 @@ build_symbol_indexes :: proc(checker: ^Checker) {
if function.generated {
continue
}
checker.function_index[function_index] = Function_Index_Entry{scope=function.pkg, name=function.name, id=ast.function_id(id)}
checker.function_index[function_index] = Function_Index_Entry{scope=function.pkg, file=function.file, hidden=function.file_hidden, name=function.name, id=ast.function_id(id)}
function_index += 1
}
slice.sort_by(checker.function_index, function_index_less)
checker.global_index = make([]Global_Index_Entry, len(checker.ast_module.globals), checker.allocator)
for global, id in checker.ast_module.globals {
checker.global_index[id] = Global_Index_Entry{scope=global.pkg, name=global.name, id=ast.global_id(id)}
checker.global_index[id] = Global_Index_Entry{scope=global.pkg, file=global.file, hidden=global.file_hidden, name=global.name, id=ast.global_id(id)}
}
slice.sort_by(checker.global_index, global_index_less)
@@ -702,12 +724,12 @@ build_symbol_indexes :: proc(checker: ^Checker) {
slice.sort_by(checker.import_index, import_index_less)
}
find_template :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(0)) -> ast.Function_Id {
return find_function_symbol(checker.function_index, pkg, name)
find_template :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(0), file := ast.INVALID_FILE) -> ast.Function_Id {
return find_function_symbol(checker.function_index, pkg, name, file)
}
find_global :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(0)) -> ast.Global_Id {
return find_global_symbol(checker.global_index, pkg, name)
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)
}
find_import :: proc(checker: ^Checker, file: ast.File_Id, alias: symbol.Id, mark_used := false) -> ast.Import_Id {
@@ -718,12 +740,26 @@ find_import :: proc(checker: ^Checker, file: ast.File_Id, alias: symbol.Id, mark
return id
}
declared_type_named :: proc(checker: ^Checker, pkg: ast.Package_Id, name: symbol.Id) -> bool {
id := types.find_named(&checker.module.types, u32(pkg), u32(name))
declared_type_named :: proc(checker: ^Checker, pkg: ast.Package_Id, name: symbol.Id, file := ast.INVALID_FILE) -> bool {
id := types.find_named(&checker.module.types, u32(pkg), u32(name), file=u32(file))
item, ok := types.node(&checker.module.types, id)
return ok && item.declared
}
declarations_conflict :: proc(left_file: ast.File_Id, left_hidden: bool, right_file: ast.File_Id, right_hidden: bool) -> bool {
return left_file == right_file if left_hidden && right_hidden else true
}
type_declaration_conflicts :: proc(checker: ^Checker, pkg: ast.Package_Id, name: symbol.Id, file: ast.File_Id, hidden: bool) -> bool {
for item in checker.module.types.nodes {
if item.declared && item.pkg == u32(pkg) && item.name == u32(name) &&
declarations_conflict(file, hidden, ast.File_Id(item.file), item.file_hidden) {
return true
}
}
return false
}
visible_name_kind :: proc(
checker: ^Checker,
name: symbol.Id,
@@ -755,13 +791,13 @@ visible_name_kind :: proc(
if find_import(checker, file, name) != ast.INVALID_IMPORT {
return "import"
}
if find_global(checker, name, pkg) != ast.INVALID_GLOBAL {
if find_global(checker, name, pkg, file) != ast.INVALID_GLOBAL {
return "global"
}
if find_template(checker, name, pkg) != ast.INVALID_FUNCTION {
if find_template(checker, name, pkg, file) != ast.INVALID_FUNCTION {
return "function"
}
if declared_type_named(checker, pkg, name) {
if declared_type_named(checker, pkg, name, file) {
return "type"
}
return ""
@@ -831,6 +867,10 @@ expr_package :: proc(checker: ^Checker, expr: ast.Expr, pkg: ast.Package_Id, fil
return import_item.target, true
}
expr_lookup_file :: proc(expr: ast.Expr, file: ast.File_Id) -> ast.File_Id {
return ast.INVALID_FILE if symbol.is_valid(expr.qualifier) else file
}
add_package_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, file: ast.File_Id) -> source.Diagnostic_Id {
if find_import(checker, file, expr.qualifier) == ast.INVALID_IMPORT {
return source.addf(checker.diagnostics, expr.span, "unknown package alias '%s'", symbol_text(checker, expr.qualifier))
@@ -838,8 +878,8 @@ add_package_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, fil
return source.addf(checker.diagnostics, expr.span, "unavailable imported package '%s'", symbol_text(checker, expr.qualifier))
}
add_name_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id) -> source.Diagnostic_Id {
if find_template(checker, expr.name, target_pkg) != ast.INVALID_FUNCTION {
add_name_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id, file: ast.File_Id) -> source.Diagnostic_Id {
if find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file)) != ast.INVALID_FUNCTION {
return source.addf(checker.diagnostics, expr.span, "'%s' is a function, not a global value", symbol_text(checker, expr.name))
}
if symbol.is_valid(expr.qualifier) {
@@ -854,8 +894,8 @@ add_name_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target
return source.addf(checker.diagnostics, expr.span, "unresolved global '%s'", symbol_text(checker, expr.name))
}
add_call_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id) -> source.Diagnostic_Id {
if find_global(checker, expr.name, target_pkg) != ast.INVALID_GLOBAL {
add_call_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id, file: ast.File_Id) -> source.Diagnostic_Id {
if find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file)) != ast.INVALID_GLOBAL {
return source.addf(checker.diagnostics, expr.span, "'%s' is a global, not a function", symbol_text(checker, expr.name))
}
if symbol.is_valid(expr.qualifier) {
@@ -976,7 +1016,7 @@ resolve_type_argument :: proc(
if !available {
return types.INVALID, false
}
value := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name))
value := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
value = types.resolve_alias(value, &checker.module.types)
return value, types.is_valid(value)
}
@@ -1963,7 +2003,7 @@ infer_compound_expr :: proc(
return types.INVALID
}
target_pkg, available := expr_package(checker, expr, pkg, file)
value := types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID
value := types.find_named(store, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file))) if available else types.INVALID
return types.resolve_alias(value, store)
case .Keyed:
return infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
@@ -2069,7 +2109,7 @@ infer_expr :: proc(
}
if !types.is_valid(last) && symbol.is_valid(expr.qualifier) &&
find_import(checker, file, expr.qualifier) == ast.INVALID_IMPORT {
if global := find_global(checker, expr.qualifier, pkg); global != ast.INVALID_GLOBAL {
if global := find_global(checker, expr.qualifier, pkg, file); global != ast.INVALID_GLOBAL {
base_type := checker.global_types[global]
item, has_item := types.container(base_type, &checker.module.types)
field_name := symbol_text(checker, expr.name)
@@ -2112,7 +2152,7 @@ infer_expr :: proc(
if !types.is_valid(last) {
target_pkg, available := expr_package(checker, expr, pkg, file)
if available {
global := find_global(checker, expr.name, target_pkg)
global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
if global != ast.INVALID_GLOBAL {
last = checker.global_types[global]
}
@@ -2121,7 +2161,7 @@ infer_expr :: proc(
if !types.is_valid(last) {
target_pkg, available := expr_package(checker, expr, pkg, file)
if available {
template := find_template(checker, expr.name, target_pkg)
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
if template != ast.INVALID_FUNCTION &&
len(checker.ast_module.functions[template].unsupported_reason) == 0 &&
checker.template_diagnostics[template] == source.INVALID_DIAGNOSTIC {
@@ -2205,7 +2245,7 @@ infer_expr :: proc(
target_pkg, available := expr_package(checker, expr, pkg, file)
template := ast.INVALID_FUNCTION
if available {
template = find_template(checker, expr.name, target_pkg)
template = find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
}
if template == ast.INVALID_FUNCTION {
callee_type := types.INVALID
@@ -2213,14 +2253,14 @@ infer_expr :: proc(
callee_type = find_infer_local(locals, expr.name)
}
if !types.is_valid(callee_type) && available {
global := find_global(checker, expr.name, target_pkg)
global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
if global != ast.INVALID_GLOBAL {
callee_type = checker.global_types[global]
}
}
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
if !ok {
distinct_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name))
distinct_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
distinct_item, distinct_ok := types.node(&checker.module.types, distinct_type)
if available && distinct_ok && distinct_item.kind == .Distinct && len(expr.args) == 1 {
stack[frame_index].left = distinct_type
@@ -2585,7 +2625,7 @@ infer_statements :: proc(
if !rhs_is_arith {
_ = record_demand(checker, statement.expr, locals^[local_index].type, locals^[:], local_types, pkg, file)
}
} else if global := find_global(checker, statement.name, pkg); global != ast.INVALID_GLOBAL {
} else if global := find_global(checker, statement.name, pkg, file); global != ast.INVALID_GLOBAL {
_ = merge_global_demand(checker, global, value_type)
if !rhs_is_arith {
_ = record_demand(checker, statement.expr, checker.global_types[global], locals^[:], local_types, pkg, file)
@@ -2945,7 +2985,7 @@ open_const_default_type :: proc(
if !available {
return types.INVALID
}
global := find_global(checker, expr.name, target_pkg)
global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
index := int(global)
if global == ast.INVALID_GLOBAL || index < 0 || index >= len(checker.global_open_const) {
return types.INVALID
@@ -3022,7 +3062,7 @@ expr_accepts_numeric_demand :: proc(
if !available {
return false
}
global := find_global(checker, expr.name, target_pkg)
global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
index := int(global)
if global == ast.INVALID_GLOBAL || index < 0 || index >= len(checker.global_open_const) {
return false
@@ -3112,7 +3152,7 @@ record_demand :: proc(
if !available {
return false
}
global := find_global(checker, expr.name, target_pkg)
global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
if global != ast.INVALID_GLOBAL {
return merge_global_demand(checker, global, demand)
}
@@ -3791,7 +3831,7 @@ infer_qualified_value_field_type :: proc(
}
base_type := find_infer_local(locals, expr.qualifier)
if !types.is_valid(base_type) {
if global := find_global(checker, expr.qualifier, pkg); global != ast.INVALID_GLOBAL {
if global := find_global(checker, expr.qualifier, pkg, file); global != ast.INVALID_GLOBAL {
base_type = checker.global_types[global]
}
}
@@ -3866,7 +3906,7 @@ build_qualified_value_field :: proc(
value, ok := build_field_from_value(checker, expr, base, local.type)
return value, true, ok
}
if global := find_global(checker, expr.qualifier, pkg); global != ast.INVALID_GLOBAL {
if global := find_global(checker, expr.qualifier, pkg, file); global != ast.INVALID_GLOBAL {
base := build_global_reference(checker, global, expr.span, global_reads)
value, ok := build_field_from_value(checker, expr, base, checker.global_types[global])
return value, true, ok
@@ -3893,7 +3933,7 @@ enum_type_from_name_expr :: proc(
find_import(checker, file, expr.qualifier) != ast.INVALID_IMPORT {
return types.INVALID, false
}
enum_type := types.find_named(&checker.module.types, u32(pkg), u32(expr.qualifier))
enum_type := types.find_named(&checker.module.types, u32(pkg), u32(expr.qualifier), file=u32(file))
return enum_type, types.is_enum(enum_type, &checker.module.types)
}
@@ -3915,7 +3955,7 @@ enum_type_from_field_expr :: proc(
if !available {
return types.INVALID, false
}
enum_type := types.find_named(&checker.module.types, u32(target_pkg), u32(base.name))
enum_type := types.find_named(&checker.module.types, u32(target_pkg), u32(base.name), file=u32(expr_lookup_file(base, file)))
return enum_type, types.is_enum(enum_type, &checker.module.types)
}
@@ -4615,7 +4655,7 @@ build_compound_expr :: proc(
struct_type := types.INVALID
if symbol.is_valid(expr.name) {
target_pkg, available := expr_package(checker, expr, pkg, file, true)
struct_type = types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID
struct_type = types.find_named(store, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file))) if available else types.INVALID
struct_type = types.resolve_alias(struct_type, store)
if !types.is_record(struct_type, store) || types.is_opaque_struct(struct_type, store) {
id := source.addf(checker.diagnostics, expr.span, "unknown or opaque record type '%s'", symbol_text(checker, expr.name))
@@ -4853,7 +4893,7 @@ build_expr :: proc(
}
if last == hir.INVALID_EXPR && symbol.is_valid(expr.qualifier) &&
find_import(checker, file, expr.qualifier) == ast.INVALID_IMPORT {
if global := find_global(checker, expr.qualifier, pkg); global != ast.INVALID_GLOBAL {
if global := find_global(checker, expr.qualifier, pkg, file); global != ast.INVALID_GLOBAL {
base := build_global_reference(checker, global, expr.span, global_reads)
base_type := checker.global_types[global]
item, has_item := types.container(base_type, &checker.module.types)
@@ -4924,16 +4964,16 @@ build_expr :: proc(
if !available {
id := add_package_resolution_diagnostic(checker, expr, file)
last = invalid_hir_expr(checker, expr.span, id)
} else if global := find_global(checker, expr.name, target_pkg); global != ast.INVALID_GLOBAL {
} else if global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file)); global != ast.INVALID_GLOBAL {
last = build_global_reference(checker, global, expr.span, global_reads)
} else {
template := find_template(checker, expr.name, target_pkg)
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
if template != ast.INVALID_FUNCTION {
last = build_function_value(checker, template, expr.span, frame.expected)
} else {
id := add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name)
if id == source.INVALID_DIAGNOSTIC {
id = add_name_resolution_diagnostic(checker, expr, target_pkg)
id = add_name_resolution_diagnostic(checker, expr, target_pkg, file)
}
last = invalid_hir_expr(checker, expr.span, id)
}
@@ -5005,7 +5045,7 @@ build_expr :: proc(
_ = pop(&stack)
continue
}
template := find_template(checker, expr.name, target_pkg)
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
if template == ast.INVALID_FUNCTION {
callee := hir.INVALID_EXPR
non_callable := false
@@ -5020,7 +5060,7 @@ build_expr :: proc(
}
}
if callee == hir.INVALID_EXPR && !non_callable {
if global := find_global(checker, expr.name, target_pkg); global != ast.INVALID_GLOBAL {
if global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file)); global != ast.INVALID_GLOBAL {
if _, _, _, callable := types.function_pointer(checker.global_types[global], &checker.module.types); callable {
callee = build_global_reference(checker, global, expr.span, global_reads)
} else {
@@ -5030,7 +5070,7 @@ build_expr :: proc(
}
}
if callee == hir.INVALID_EXPR {
distinct_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name))
distinct_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
distinct_item, distinct_ok := types.node(&checker.module.types, distinct_type)
if distinct_ok && distinct_item.kind == .Distinct {
if !is_runtime_type(checker, distinct_type) {
@@ -5067,12 +5107,12 @@ build_expr :: proc(
}
id := source.INVALID_DIAGNOSTIC
if non_callable {
id = add_call_resolution_diagnostic(checker, expr, target_pkg) if non_callable_global else
id = add_call_resolution_diagnostic(checker, expr, target_pkg, file) if non_callable_global else
source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
} else {
id = add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name)
if id == source.INVALID_DIAGNOSTIC {
id = add_call_resolution_diagnostic(checker, expr, target_pkg)
id = add_call_resolution_diagnostic(checker, expr, target_pkg, file)
}
}
last = invalid_hir_expr(checker, expr.span, id)
@@ -5475,6 +5515,9 @@ make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
defer strings.builder_destroy(&builder)
strings.write_string(&builder, "bro_c__" if function.c_abi else "bro__")
fmt.sbprintf(&builder, "p%d__", function.pkg)
if function.file_hidden {
fmt.sbprintf(&builder, "f%d__", function.file)
}
strings.write_string(&builder, symbol_text(checker, function.name))
for arg in spec.args {
strings.write_string(&builder, "__")
@@ -5823,7 +5866,7 @@ build_block :: proc(
}
local, found := find_build_local(ctx.locals^[:], statement.name)
if !found {
global := find_global(checker, statement.name, ctx.pkg)
global := find_global(checker, statement.name, ctx.pkg, ctx.file)
if global == ast.INVALID_GLOBAL {
id := source.addf(checker.diagnostics, statement.span, "cannot assign unresolved local '%s'", symbol_text(checker, statement.name))
append(&body, hir.stmt_id(len(checker.module.statements)))
@@ -5967,7 +6010,7 @@ build_block :: proc(
}
} else if expr_ast.kind == .Struct_Literal {
target_pkg, available := expr_package(checker, expr_ast, ctx.pkg, ctx.file, true)
named := types.find_named(store, u32(target_pkg), u32(expr_ast.name)) if available else types.INVALID
named := types.find_named(store, u32(target_pkg), u32(expr_ast.name), file=u32(expr_lookup_file(expr_ast, ctx.file))) if available else types.INVALID
named = types.resolve_alias(named, store)
error_path = can_implicitly_convert_type(checker, named, error_type)
} else {
@@ -8688,26 +8731,29 @@ check :: proc(
continue
}
for previous in ast_module.functions[:index] {
if !previous.generated && previous.pkg == function.pkg && previous.name == function.name {
if !previous.generated && previous.pkg == function.pkg && previous.name == function.name &&
declarations_conflict(function.file, function.file_hidden, previous.file, previous.file_hidden) {
source.addf(diagnostics, function.span, "duplicate function '%s'", symbol_text(&checker, function.name))
}
}
for global in ast_module.globals {
if global.pkg == function.pkg && global.name == function.name {
if global.pkg == function.pkg && global.name == function.name &&
declarations_conflict(function.file, function.file_hidden, global.file, global.file_hidden) {
source.addf(diagnostics, function.span, "package declaration '%s' conflicts with a global", symbol_text(&checker, function.name))
}
}
if declared_type_named(&checker, function.pkg, function.name) {
if type_declaration_conflicts(&checker, function.pkg, function.name, function.file, function.file_hidden) {
source.addf(diagnostics, function.span, "function '%s' shadows visible type", symbol_text(&checker, function.name))
}
}
for global, index in ast_module.globals {
for previous in ast_module.globals[:index] {
if previous.pkg == global.pkg && previous.name == global.name {
if previous.pkg == global.pkg && previous.name == global.name &&
declarations_conflict(global.file, global.file_hidden, previous.file, previous.file_hidden) {
source.addf(diagnostics, global.span, "duplicate global '%s'", symbol_text(&checker, global.name))
}
}
if declared_type_named(&checker, global.pkg, global.name) {
if type_declaration_conflicts(&checker, global.pkg, global.name, global.file, global.file_hidden) {
source.addf(diagnostics, global.span, "global '%s' shadows visible type", symbol_text(&checker, global.name))
}
}
+6 -6
View File
@@ -933,9 +933,9 @@ ct_eval_expr :: proc(
if !available {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "unavailable imported package")
}
global := find_global(checker, expr.name, target_pkg)
global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, state.file))
if global == ast.INVALID_GLOBAL || int(global) >= len(checker.ast_module.globals) {
template := find_template(checker, expr.name, target_pkg)
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, state.file))
if template != ast.INVALID_FUNCTION {
pointer_type, _, function_ok := function_pointer_type_for_template(
checker,
@@ -1218,7 +1218,7 @@ ct_eval_struct_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Ty
struct_type := types.INVALID
if symbol.is_valid(expr.name) {
target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false)
struct_type = types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID
struct_type = types.find_named(store, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, state.file))) if available else types.INVALID
struct_type = types.resolve_alias(struct_type, store)
} else {
struct_type = types.resolve_alias(expected, store)
@@ -1622,7 +1622,7 @@ ct_eval_place :: proc(
if !available {
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "unavailable imported package")
}
global := find_global(checker, expr.name, target_pkg)
global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, state.file))
if global == ast.INVALID_GLOBAL || int(global) >= len(checker.ast_module.globals) {
return INVALID_CT_PLACE, types.INVALID, false, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unresolved comptime value '%s'", symbol_text(checker, expr.name))
}
@@ -1926,7 +1926,7 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
if !available {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "unavailable function package")
}
template := find_template(checker, expr.name, target_pkg)
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, state.file))
if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) {
if !symbol.is_valid(expr.qualifier) {
if index, ok := ct_find_binding_index(state, expr.name); ok {
@@ -1936,7 +1936,7 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
}
}
}
global := find_global(checker, expr.name, target_pkg)
global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, state.file))
if global != ast.INVALID_GLOBAL && int(global) < len(checker.ast_module.globals) {
g := checker.ast_module.globals[global]
if !g.external && g.immutable && !g.writable {
+5 -5
View File
@@ -1226,18 +1226,18 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r
return pkg_id
}
declaration_conflicts :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> bool {
declaration_conflicts :: proc(module: ^ast.Module, pkg: ast.Package_Id, file: ast.File_Id, name: symbol.Id) -> bool {
for function in module.functions {
if function.pkg == pkg && function.name == name {
if function.pkg == pkg && function.name == name && (!function.file_hidden || function.file == file) {
return true
}
}
for global in module.globals {
if global.pkg == pkg && global.name == name {
if global.pkg == pkg && global.name == name && (!global.file_hidden || global.file == file) {
return true
}
}
type_id := types.find_named(&module.type_store, u32(pkg), u32(name))
type_id := types.find_named(&module.type_store, u32(pkg), u32(name), file=u32(file))
type_item, type_ok := types.node(&module.type_store, type_id)
if type_ok && type_item.declared {
return true
@@ -1260,7 +1260,7 @@ validate_imports :: proc(state: ^State) {
)
state.module.imports[import_id].valid = false
}
if declaration_conflicts(state.module, import_item.pkg, alias) {
if declaration_conflicts(state.module, import_item.pkg, import_item.file, alias) {
state.module.imports[import_id].diagnostic = source.addf(
state.diagnostics,
import_item.span,
+14 -5
View File
@@ -37,6 +37,11 @@ token_text :: proc(parser: ^Parser, tok: token.Token) -> string {
return parser.source_file.text[int(tok.span.start):int(tok.span.end)]
}
file_hidden_name :: proc(parser: ^Parser, tok: token.Token) -> bool {
name := token_text(parser, tok)
return len(name) > 1 && name[0] == '_'
}
span_from :: proc(first, last: source.Span) -> source.Span {
return source.Span{file=first.file, start=first.start, end=last.end}
}
@@ -379,6 +384,7 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
u32(name.symbol),
u32(qualifier),
u32(parser.file),
!symbol.is_valid(qualifier) && file_hidden_name(parser, name),
)
}
source.add(parser.diagnostics, tok.span, "expected a type")
@@ -2108,6 +2114,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
pkg=parser.pkg,
file=parser.file,
c_abi=c_abi,
file_hidden=!c_abi && file_hidden_name(parser, name),
has_body=false,
variadic=variadic,
params=params,
@@ -2126,6 +2133,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
pkg=parser.pkg,
file=parser.file,
c_abi=c_abi,
file_hidden=!c_abi && file_hidden_name(parser, name),
has_body=true,
variadic=variadic,
params=params,
@@ -2272,7 +2280,7 @@ parse_inline_union_type :: proc(parser: ^Parser) -> types.Type {
parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, is_union := false) {
start := advance(parser)
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol))
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=!c_layout && file_hidden_name(parser, name))
// A tagged union spells its discriminant in parens: `union(Enum)` reuses an existing
// enum; `union(enum)` synthesizes one from the variant names after the body is parsed.
tag := types.INVALID
@@ -2330,7 +2338,7 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, is_unio
parse_opaque :: proc(parser: ^Parser, name: token.Token) {
start := advance(parser)
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol))
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden_name(parser, name))
if !types.define_record(&parser.module.type_store, id, nil, false, true, false) {
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
}
@@ -2359,7 +2367,7 @@ synthesize_union_tag :: proc(parser: ^Parser, fields: []types.Field) -> types.Ty
parse_distinct :: proc(parser: ^Parser, name: token.Token) {
start := advance(parser)
child := parse_type(parser)
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol))
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden_name(parser, name))
if !types.define_distinct(&parser.module.type_store, id, child) {
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
}
@@ -2372,7 +2380,7 @@ parse_distinct :: proc(parser: ^Parser, name: token.Token) {
parse_alias :: proc(parser: ^Parser, name: token.Token) {
start := advance(parser)
child := parse_type(parser)
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol))
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden_name(parser, name))
if !types.define_alias(&parser.module.type_store, id, child) {
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
}
@@ -2519,7 +2527,7 @@ parse_enum :: proc(parser: ^Parser, name: token.Token) {
_ = finish_statement(parser)
return
}
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol))
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden_name(parser, name))
if !types.define_enum(&parser.module.type_store, id, backing, members[:], explicit_backing) {
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
}
@@ -2699,6 +2707,7 @@ parse_top_level :: proc(parser: ^Parser) {
name=name.symbol,
pkg=parser.pkg,
file=parser.file,
file_hidden=file_hidden_name(parser, name),
type=type_syntax,
immutable=operator.kind == .Colon_Colon,
expr=expr,
+19 -6
View File
@@ -102,6 +102,7 @@ Node :: struct {
c_layout: bool,
opaque: bool,
declared: bool,
file_hidden: bool,
explicit_backing: bool,
}
@@ -185,24 +186,36 @@ intern :: proc(store: ^Store, candidate: Node) -> Type {
return id
}
named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff) -> Type {
normalized_file := file if qualifier != 0 else u32(0)
named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff, file_hidden := false) -> Type {
normalized_file := file if qualifier != 0 || file_hidden else u32(0)
for existing, index in store.nodes {
visibility_matches := existing.file_hidden == file_hidden &&
(!file_hidden || existing.file == normalized_file)
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Distinct ||
existing.kind == .Enum || existing.kind == .Struct || existing.kind == .Union) &&
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier &&
existing.file == normalized_file {
(visibility_matches || qualifier == 0 && existing.file_hidden != file_hidden) {
return DYNAMIC_START+Type(index)
}
}
return intern(store, Node{kind=.Named, pkg=pkg, name=name, qualifier=qualifier, file=normalized_file})
return intern(store, Node{kind=.Named, pkg=pkg, name=name, qualifier=qualifier, file=normalized_file, file_hidden=file_hidden})
}
find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0) -> Type {
find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff) -> Type {
if file != 0xffff_ffff {
for existing, index in store.nodes {
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Distinct ||
existing.kind == .Enum || existing.kind == .Struct || existing.kind == .Union) &&
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier &&
existing.file_hidden && existing.file == file {
return DYNAMIC_START+Type(index)
}
}
}
for existing, index in store.nodes {
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Distinct ||
existing.kind == .Enum || existing.kind == .Struct || existing.kind == .Union) &&
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier {
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier && !existing.file_hidden {
return DYNAMIC_START+Type(index)
}
}
+43
View File
@@ -6645,6 +6645,49 @@ imports_are_file_local :: proc(t: ^testing.T) {
testing.expect(t, !state.success)
}
@(test)
leading_underscore_declarations_are_file_hidden :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-hidden-declarations"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/packages/hidden_valid/app", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 7)
}
@(test)
file_hidden_declarations_are_not_package_members :: proc(t: ^testing.T) {
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
module, loaded := loader.load("examples/packages/hidden_invalid/app", &sources, &diagnostics, &symbols)
defer ast.destroy_module(&module)
hir_module := checker.check(&module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
testing.expect(t, loaded)
found_sibling := false
found_sibling_value := false
found_sibling_type := false
found_import := false
found_collision := false
for diagnostic in diagnostics.items {
found_sibling = found_sibling || strings.contains(diagnostic.message, "unresolved function '_sibling'")
found_sibling_value = found_sibling_value || strings.contains(diagnostic.message, "unresolved global '_sibling_value'")
found_sibling_type = found_sibling_type || strings.contains(diagnostic.message, "unknown or opaque record type '_Sibling'")
found_import = found_import || strings.contains(diagnostic.message, "package 'dep' has no member '_secret'")
found_collision = found_collision || strings.contains(diagnostic.message, "duplicate function '_collision'")
}
testing.expect(t, found_sibling)
testing.expect(t, found_sibling_value)
testing.expect(t, found_sibling_type)
testing.expect(t, found_import)
testing.expect(t, found_collision)
}
@(test)
package_import_cycles_are_valid :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-cycle"
@@ -0,0 +1,13 @@
_sibling func() i32 {
return 1
}
_Sibling :: struct {
value i32
}
_sibling_value :: 1
_collision c_func() i32 {
return 1
}
@@ -0,0 +1,17 @@
import "../dep"
_collision func() i32 {
return 2
}
read_sibling func(value _Sibling) i32 {
return value.value + _sibling_value
}
read_sibling_value func() i32 {
return _sibling_value
}
main func() i32 {
return _sibling() + dep._secret() + read_sibling(_Sibling { value = 1 }) + read_sibling_value()
}
@@ -0,0 +1,3 @@
_secret func() i32 {
return 1
}
+22
View File
@@ -0,0 +1,22 @@
_Thing :: struct {
value i32
}
_value :: 1
_helper func() i32 {
thing _Thing = _Thing { value = _value }
return thing.value
}
_foreign c_func() i32 {
return 1
}
_C_Record :: c_struct {
value c_int
}
from_a func() i32 {
return _helper()
}
+20
View File
@@ -0,0 +1,20 @@
_Thing :: struct {
value i32
}
_value :: 2
_helper func() i32 {
thing _Thing = _Thing { value = _value }
return thing.value
}
Box :: struct {
_value i32
}
from_b func(_input i32) i32 {
_local Box = Box { _value = _input }
record _C_Record = _C_Record { value = 0 }
return _helper() + _local._value + _foreign() + i32(record.value)
}
@@ -0,0 +1,3 @@
main func() i32 {
return from_a() + from_b(3)
}
+12 -12
View File
@@ -7,6 +7,18 @@ Allocator :: struct {
free @func(context ?*mut anyopaque, memory ?*mut u8, size usize, alignment usize) void
}
alloc func(allocator Allocator, size usize, alignment usize) ?*mut u8 {
return allocator.alloc(allocator.context, size, alignment)
}
realloc func(allocator Allocator, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
return allocator.realloc(allocator.context, memory, old_size, new_size, alignment)
}
free func(allocator Allocator, memory ?*mut u8, size usize, alignment usize) void {
allocator.free(allocator.context, memory, size, alignment)
}
_malloc_alignment usize :: 16 # ponytail: aarch64-macos libc malloc alignment assumption.
_power_of_two func(value usize) bool {
@@ -87,15 +99,3 @@ c_allocator Allocator :: Allocator {
realloc = _c_realloc,
free = _c_free,
}
alloc func(allocator Allocator, size usize, alignment usize) ?*mut u8 {
return allocator.alloc(allocator.context, size, alignment)
}
realloc func(allocator Allocator, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
return allocator.realloc(allocator.context, memory, old_size, new_size, alignment)
}
free func(allocator Allocator, memory ?*mut u8, size usize, alignment usize) void {
allocator.free(allocator.context, memory, size, alignment)
}