package-private visibility

This commit is contained in:
2026-08-01 22:55:35 +02:00
parent 304880a9aa
commit f267e8c3cb
11 changed files with 301 additions and 185 deletions
+128 -53
View File
@@ -139,7 +139,6 @@ Build_Ctx :: struct {
Function_Index_Entry :: struct {
scope: ast.Package_Id,
file: ast.File_Id,
hidden: bool,
name: symbol.Id,
id: ast.Function_Id,
@@ -147,7 +146,6 @@ Function_Index_Entry :: struct {
Global_Index_Entry :: struct {
scope: ast.Package_Id,
file: ast.File_Id,
hidden: bool,
name: symbol.Id,
id: ast.Global_Id,
@@ -1508,7 +1506,7 @@ find_function_symbol :: proc(index: []Function_Index_Entry, scope: ast.Package_I
for low < len(index) && index[low].scope == scope && index[low].name == name {
entry := index[low]
if entry.hidden {
if entry.file == file {
if file != ast.INVALID_FILE {
return entry.id
}
} else {
@@ -1535,7 +1533,7 @@ find_global_symbol :: proc(index: []Global_Index_Entry, scope: ast.Package_Id, n
for low < len(index) && index[low].scope == scope && index[low].name == name {
entry := index[low]
if entry.hidden {
if entry.file == file {
if file != ast.INVALID_FILE {
return entry.id
}
} else {
@@ -1582,12 +1580,12 @@ build_symbol_indexes :: proc(checker: ^Checker) {
if function.generated {
continue
}
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)}
checker.function_index[function_index] = Function_Index_Entry{scope=function.pkg, hidden=function.package_hidden, name=function.name, id=ast.function_id(id)}
function_index += 1
}
for alias in checker.ast_module.aliases {
if alias.valid && alias.kind == .Function {
checker.function_index[function_index] = Function_Index_Entry{scope=alias.pkg, file=alias.file, hidden=alias.file_hidden, name=alias.name, id=ast.Function_Id(alias.target)}
checker.function_index[function_index] = Function_Index_Entry{scope=alias.pkg, hidden=alias.package_hidden, name=alias.name, id=ast.Function_Id(alias.target)}
function_index += 1
}
}
@@ -1601,12 +1599,12 @@ build_symbol_indexes :: proc(checker: ^Checker) {
}
checker.global_index = make([]Global_Index_Entry, global_count, checker.allocator)
for global, id in checker.ast_module.globals {
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)}
checker.global_index[id] = Global_Index_Entry{scope=global.pkg, hidden=global.package_hidden, name=global.name, id=ast.global_id(id)}
}
global_index := len(checker.ast_module.globals)
for alias in checker.ast_module.aliases {
if alias.valid && alias.kind == .Global {
checker.global_index[global_index] = Global_Index_Entry{scope=alias.pkg, file=alias.file, hidden=alias.file_hidden, name=alias.name, id=ast.Global_Id(alias.target)}
checker.global_index[global_index] = Global_Index_Entry{scope=alias.pkg, hidden=alias.package_hidden, name=alias.name, id=ast.Global_Id(alias.target)}
global_index += 1
}
}
@@ -1707,7 +1705,7 @@ configure_entry_point :: proc(checker: ^Checker) {
type_from_syntax(checker, function.result, function.pkg, function.file),
&checker.module.types,
)
if function.file_hidden && function.has_body && !function.c_abi && len(function.params) == 0 &&
if function.package_hidden && 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)
}
@@ -1743,14 +1741,10 @@ declared_type_named :: proc(checker: ^Checker, pkg: ast.Package_Id, name: symbol
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 {
type_declaration_conflicts :: proc(checker: ^Checker, pkg: ast.Package_Id, name: symbol.Id) -> 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) {
if item.declared && item.pkg == u32(pkg) && item.name == u32(name) {
return true
}
}
@@ -2386,6 +2380,28 @@ call_argument_mapping :: proc(
return selected, .Explicit if identity else .Inferred, comptime_param_count(function^), ""
}
inferred_comptime_value_equal :: proc(checker: ^Checker, left, right: Comptime_Value) -> bool {
if left.kind != right.kind {
return false
}
if left.kind == .Type {
return types.equal(
types.resolve_alias(left.type, &checker.module.types),
types.resolve_alias(right.type, &checker.module.types),
)
}
if !types.equal(left.type, right.type) {
return false
}
if left.kind == .Static {
return left.fingerprint == right.fingerprint && left.key == right.key
}
if left.kind == .String {
return left.text == right.text
}
return left.value == right.value
}
bind_inferred_comptime :: proc(
checker: ^Checker,
function: ast.Function,
@@ -2409,16 +2425,7 @@ bind_inferred_comptime :: proc(
return true
}
existing := values[index]
matches := existing.kind == value.kind
if matches {
if existing.kind == .Type {
matches = types.equal(types.resolve_alias(existing.type, &checker.module.types), types.resolve_alias(value.type, &checker.module.types))
} else if existing.kind == .Static {
matches = existing.fingerprint == value.fingerprint && existing.key == value.key && types.equal(existing.type, value.type)
} else {
matches = existing.value == value.value && types.equal(existing.type, value.type)
}
}
matches := inferred_comptime_value_equal(checker, existing, value)
if !matches && diagnose {
left := type_label(checker, existing.type) if existing.kind == .Type else fmt.aprintf("<comptime value>", allocator=checker.allocator) if existing.kind == .Static else fmt.aprintf("%d", existing.value, allocator=checker.allocator)
right := type_label(checker, value.type) if value.kind == .Type else fmt.aprintf("<comptime value>", allocator=checker.allocator) if value.kind == .Static else fmt.aprintf("%d", value.value, allocator=checker.allocator)
@@ -2514,6 +2521,33 @@ type_pattern_mentions_comptime :: proc(
return false
}
type_pattern_contains_type_call :: proc(checker: ^Checker, pattern: types.Type, depth := 0) -> bool {
if depth > 64 {
return false
}
item, ok := types.node(&checker.module.types, pattern)
if !ok {
return false
}
if item.kind == .Type_Call {
return true
}
if types.is_valid(item.child) && type_pattern_contains_type_call(checker, item.child, depth+1) {
return true
}
if types.is_valid(item.extra) && type_pattern_contains_type_call(checker, item.extra, depth+1) {
return true
}
if item.kind == .Function {
for field in types.params_for(&checker.module.types, pattern) {
if type_pattern_contains_type_call(checker, field.type, depth+1) {
return true
}
}
}
return false
}
match_inferred_type_pattern :: proc(
checker: ^Checker,
function: ast.Function,
@@ -2563,22 +2597,52 @@ match_inferred_type_pattern :: proc(
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, function.file))
origin: Type_Factory_Origin
found := false
ambiguous := false
for candidate in checker.type_factory_origins {
if candidate.template == template && types.equal(candidate.result, actual_type) {
if candidate.template != template || !types.equal(candidate.result, actual_type) ||
len(expr.args) != len(candidate.values) {
continue
}
compatible := true
for arg_id, index in expr.args {
if arg_id == ast.INVALID_EXPR || int(arg_id) >= len(checker.ast_module.exprs) {
compatible = false
break
}
arg := checker.ast_module.exprs[arg_id]
if arg.kind == .Name && !symbol.is_valid(arg.qualifier) {
if binding_index, is_binding := comptime_binding_index(function, prefix, arg.name);
is_binding && bound[binding_index] &&
!inferred_comptime_value_equal(checker, values[binding_index], candidate.values[index]) {
compatible = false
break
}
}
}
if !compatible {
continue
}
if !found {
origin = candidate
found = true
break
continue
}
for arg_id, index in expr.args {
arg := checker.ast_module.exprs[arg_id]
if arg.kind == .Name && !symbol.is_valid(arg.qualifier) {
if _, is_binding := comptime_binding_index(function, prefix, arg.name);
is_binding && !inferred_comptime_value_equal(checker, origin.values[index], candidate.values[index]) {
ambiguous = true
break
}
}
}
}
if !found || len(expr.args) != len(origin.values) {
if !found || ambiguous {
return false
}
matched := true
for arg_id, index in expr.args {
if arg_id == ast.INVALID_EXPR || int(arg_id) >= len(checker.ast_module.exprs) {
matched = false
continue
}
arg := checker.ast_module.exprs[arg_id]
if arg.kind == .Name && !symbol.is_valid(arg.qualifier) {
if _, is_binding := comptime_binding_index(function, prefix, arg.name); is_binding {
@@ -2893,11 +2957,21 @@ infer_call_comptime_values :: proc(
for value_bound in bound {
all_bound = all_bound && value_bound
}
// Concrete arguments bind first. Numeric constants, string literals, and `null`
// are contextual and therefore only contribute after stronger evidence has had a
// chance to bind the parameter type.
weak_passes := [2]bool{false, true}
for weak in weak_passes {
// Factory provenance is authoritative when unique, so try it before direct
// arguments. Strong and then weak direct evidence can disambiguate a factory
// result; retry factory patterns once after both evidence passes.
Evidence_Pass :: struct {
type_call: bool,
weak: bool,
final: bool,
}
evidence_passes := [?]Evidence_Pass{
{type_call=true},
{final=true},
{weak=true, final=true},
{type_call=true, final=true},
}
for pass in evidence_passes {
for arg_id, source_index in args {
param_index := call_param_index(mapping, source_index)
if param_index >= len(function.params) || param_index >= len(actual_args) {
@@ -2910,7 +2984,11 @@ infer_call_comptime_values :: proc(
is_null := arg_expr.kind == .Null
is_weak := is_numeric_constant_expr(checker, arg_id) ||
arg_expr.kind == .String || is_null
if is_weak != weak {
contains_type_call := type_pattern_contains_type_call(
checker, function.params[param_index].type,
)
if contains_type_call != pass.type_call ||
!pass.type_call && is_weak != pass.weak {
continue
}
if !type_pattern_mentions_comptime(checker, function, prefix, function.params[param_index].type) {
@@ -2923,7 +3001,7 @@ infer_call_comptime_values :: proc(
if all_bound && arg_expr.kind == .Struct_Literal && !arg_expr.tuple && !symbol.is_valid(arg_expr.name) {
continue
}
if weak {
if pass.weak {
if item, ok := types.node(&checker.module.types, function.params[param_index].type); ok &&
item.qualifier == 0 && item.name != 0 {
if binding_index, is_binding := comptime_binding_index(function, prefix, symbol.Id(item.name));
@@ -2945,11 +3023,14 @@ infer_call_comptime_values :: proc(
actual_args[param_index] = contextual
}
}
matched = match_inferred_type_pattern(
pass_matched := match_inferred_type_pattern(
checker, function, prefix, function.params[param_index].type,
actual, values, bound,
checker.ast_module.exprs[arg_id].span, diagnose,
) && matched
checker.ast_module.exprs[arg_id].span, diagnose && pass.final,
)
if pass.final {
matched = pass_matched && matched
}
}
}
ordinal = 0
@@ -10063,9 +10144,6 @@ 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, "__")
@@ -14381,29 +14459,26 @@ check :: proc(
continue
}
for previous in ast_module.functions[:index] {
if !previous.generated && previous.pkg == function.pkg && previous.name == function.name &&
declarations_conflict(function.file, function.file_hidden, previous.file, previous.file_hidden) {
if !previous.generated && previous.pkg == function.pkg && previous.name == function.name {
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 &&
declarations_conflict(function.file, function.file_hidden, global.file, global.file_hidden) {
if global.pkg == function.pkg && global.name == function.name {
source.addf(diagnostics, function.span, "package declaration '%s' conflicts with a global", symbol_text(&checker, function.name))
}
}
if type_declaration_conflicts(&checker, function.pkg, function.name, function.file, function.file_hidden) {
if type_declaration_conflicts(&checker, function.pkg, function.name) {
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 &&
declarations_conflict(global.file, global.file_hidden, previous.file, previous.file_hidden) {
if previous.pkg == global.pkg && previous.name == global.name {
source.addf(diagnostics, global.span, "duplicate global '%s'", symbol_text(&checker, global.name))
}
}
if type_declaration_conflicts(&checker, global.pkg, global.name, global.file, global.file_hidden) {
if type_declaration_conflicts(&checker, global.pkg, global.name) {
source.addf(diagnostics, global.span, "global '%s' shadows visible type", symbol_text(&checker, global.name))
}
}