From f267e8c3cb771da56506c85e94f585defc8f020e Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Sat, 1 Aug 2026 22:55:35 +0200 Subject: [PATCH] package-private visibility --- LANGUAGE.md | 2 +- README.md | 5 +- TODO.md | 8 +- compiler/ast/ast.odin | 6 +- compiler/checker/checker.odin | 181 ++++++++++++++++------- compiler/loader/loader.odin | 42 +++--- compiler/parser/parser.odin | 74 ++++----- compiler/testing.odin | 1 + compiler/types/types.odin | 42 +++--- compiler_tests.odin | 91 +++++++++++- examples/packages/hidden_valid/app/b.bro | 34 +---- 11 files changed, 301 insertions(+), 185 deletions(-) diff --git a/LANGUAGE.md b/LANGUAGE.md index 4bf23e5..169128d 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -15,7 +15,7 @@ roadmap and milestone history. - file-local relative imports, import aliases, and qualified member access - transparent declaration aliases with `Name :: alias package.Member`; functions/type factories, named types, and globals retain their original declaration or storage identity -- `hide` makes any named top-level declaration file-local; declarations are public by default, +- `hide` makes any named top-level declaration package-local; declarations are public by default, leading underscores are ordinary identifier characters, and imports are always file-local - relative `.h` imports as synthetic C header package namespaces - native `name test { ... }` declarations with fallible-void results inferred from `testing.Error` diff --git a/README.md b/README.md index e1781d4..fcf627d 100644 --- a/README.md +++ b/README.md @@ -207,8 +207,9 @@ value :: math.sum(other_math.value, 1) ``` Top-level declarations are public by default. Prefix a declaration with `hide` -to keep it local to its source file; leading underscores have no visibility -meaning. Imports are always file-local and cannot be hidden or re-exported: +to keep it local to its package; sibling files can use it, but importing packages +cannot. Leading underscores have no visibility meaning. Imports are always file-local +and cannot be hidden or re-exported: ```bro hide helper func() i32 { return 42 } diff --git a/TODO.md b/TODO.md index 670a3fd..c5ca540 100644 --- a/TODO.md +++ b/TODO.md @@ -842,13 +842,13 @@ - this keeps package lookup shallow and deterministic and avoids overloading import aliases with declaration visibility -36.5. explicit `hide` file-local declarations (implemented) +36.5. explicit `hide` package-local declarations (implemented) - `hide name ...` gives any named top-level function, global, native/C record, union, enum, - opaque/distinct type, or declaration/type alias the existing file-local semantics + opaque/distinct type, or declaration/type alias package-local visibility across sibling files - declarations remain public by default; a leading underscore is an ordinary identifier and `_` remains the write-only sink - - hidden native and C declarations with the same name may coexist in separate files, while public - collisions are still diagnosed + - hidden declarations occupy the package namespace, collide with sibling declarations of the + same name, and remain absent from imported package namespaces - `hide` is reserved for named top-level declarations and is rejected on imports, locals, parameters, fields, and anonymous declarations diff --git a/compiler/ast/ast.odin b/compiler/ast/ast.odin index f3bda73..6d5e82d 100644 --- a/compiler/ast/ast.odin +++ b/compiler/ast/ast.odin @@ -243,7 +243,7 @@ Function :: struct { generated: bool, analysis_root: bool, test: bool, - file_hidden: bool, + package_hidden: bool, has_body: bool, variadic: bool, params: []Param, @@ -265,7 +265,7 @@ Global :: struct { immutable: bool, external: bool, writable: bool, - file_hidden: bool, + package_hidden: bool, expr: Expr_Id, diagnostic: source.Diagnostic_Id, } @@ -313,7 +313,7 @@ Declaration_Alias :: struct { target_pkg: Package_Id, target: u32, kind: Declaration_Alias_Kind, - file_hidden: bool, + package_hidden: bool, valid: bool, diagnostic: source.Diagnostic_Id, } diff --git a/compiler/checker/checker.odin b/compiler/checker/checker.odin index 403475d..091cf78 100644 --- a/compiler/checker/checker.odin +++ b/compiler/checker/checker.odin @@ -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("", 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("", 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)) } } diff --git a/compiler/loader/loader.odin b/compiler/loader/loader.odin index 6f3427a..2d3af7d 100644 --- a/compiler/loader/loader.odin +++ b/compiler/loader/loader.odin @@ -1251,12 +1251,12 @@ load_package :: proc( 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 && (!function.file_hidden || function.file == file) { + if function.pkg == pkg && function.name == name { return true } } for global in module.globals { - if global.pkg == pkg && global.name == name && (!global.file_hidden || global.file == file) { + if global.pkg == pkg && global.name == name { return true } } @@ -1327,8 +1327,7 @@ find_visible_enum_global :: proc( public_only := false, ) -> ast.Global_Id { for global, index in module.globals { - if global.pkg == pkg && global.name == name && - (!global.file_hidden || !public_only && global.file == file) { + if global.pkg == pkg && global.name == name && (!global.package_hidden || !public_only) { return ast.global_id(index) } } @@ -1449,26 +1448,20 @@ resolve_enum_values :: proc(state: ^State) { } } -alias_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 -} alias_conflicts_with_declaration :: proc(module: ^ast.Module, alias: ast.Declaration_Alias) -> bool { for function in module.functions { - if function.pkg == alias.pkg && function.name == alias.name && - alias_declarations_conflict(alias.file, alias.file_hidden, function.file, function.file_hidden) { + if function.pkg == alias.pkg && function.name == alias.name { return true } } for global in module.globals { - if global.pkg == alias.pkg && global.name == alias.name && - alias_declarations_conflict(alias.file, alias.file_hidden, global.file, global.file_hidden) { + if global.pkg == alias.pkg && global.name == alias.name { return true } } for item in module.type_store.nodes { - if item.declared && item.pkg == u32(alias.pkg) && item.name == u32(alias.name) && - alias_declarations_conflict(alias.file, alias.file_hidden, ast.File_Id(item.file), item.file_hidden) { + if item.declared && item.pkg == u32(alias.pkg) && item.name == u32(alias.name) { return true } } @@ -1480,7 +1473,7 @@ direct_alias_target :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symb target: u32 kinds := 0 for function, index in module.functions { - if function.pkg == pkg && function.name == name && !function.generated && !function.file_hidden { + if function.pkg == pkg && function.name == name && !function.generated && !function.package_hidden { kind = .Function target = u32(ast.function_id(index)) kinds += 1 @@ -1488,7 +1481,7 @@ direct_alias_target :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symb } } for global, index in module.globals { - if global.pkg == pkg && global.name == name && !global.file_hidden { + if global.pkg == pkg && global.name == name && !global.package_hidden { kind = .Global target = u32(ast.global_id(index)) kinds += 1 @@ -1507,22 +1500,22 @@ direct_alias_target :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symb hidden_alias_target_exists :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> bool { for function in module.functions { - if function.pkg == pkg && function.name == name && function.file_hidden { + if function.pkg == pkg && function.name == name && function.package_hidden { return true } } for global in module.globals { - if global.pkg == pkg && global.name == name && global.file_hidden { + if global.pkg == pkg && global.name == name && global.package_hidden { return true } } for item in module.type_store.nodes { - if item.declared && item.pkg == u32(pkg) && item.name == u32(name) && item.file_hidden { + if item.declared && item.pkg == u32(pkg) && item.name == u32(name) && item.package_hidden { return true } } for alias in module.aliases { - if alias.valid && alias.pkg == pkg && alias.name == name && alias.file_hidden { + if alias.valid && alias.pkg == pkg && alias.name == name && alias.package_hidden { return true } } @@ -1531,7 +1524,7 @@ hidden_alias_target_exists :: proc(module: ^ast.Module, pkg: ast.Package_Id, nam find_public_alias :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> int { for alias, index in module.aliases { - if alias.valid && alias.pkg == pkg && alias.name == name && !alias.file_hidden { + if alias.valid && alias.pkg == pkg && alias.name == name && !alias.package_hidden { return index } } @@ -1592,7 +1585,7 @@ resolve_declaration_alias :: proc(state: ^State, index: int, states: []u8) -> bo alias.diagnostic = source.addf( state.diagnostics, alias.span, - "package member '%s.%s' is file-hidden", + "package member '%s.%s' is package-hidden", symbol.resolve(state.symbols, alias.qualifier), symbol.resolve(state.symbols, alias.member), ) @@ -1618,8 +1611,7 @@ validate_declaration_aliases :: proc(state: ^State) { continue } for previous in state.module.aliases[:index] { - if previous.pkg == alias.pkg && previous.name == alias.name && - alias_declarations_conflict(alias.file, alias.file_hidden, previous.file, previous.file_hidden) { + if previous.pkg == alias.pkg && previous.name == alias.name { alias.diagnostic = source.addf(state.diagnostics, alias.span, "duplicate declaration alias '%s'", name) alias.valid = false break @@ -1669,9 +1661,9 @@ validate_declaration_aliases :: proc(state: ^State) { u32(alias.pkg), u32(alias.name), file=u32(alias.file), - file_hidden=alias.file_hidden, + package_hidden=alias.package_hidden, ) - if !types.define_alias(&state.module.type_store, id, types.Type(alias.target)) { + if !types.define_alias(&state.module.type_store, id, types.Type(alias.target), alias.package_hidden) { alias.diagnostic = source.addf(state.diagnostics, alias.span, "duplicate type declaration '%s'", symbol.resolve(state.symbols, alias.name)) alias.valid = false } diff --git a/compiler/parser/parser.odin b/compiler/parser/parser.odin index 0f971eb..ba4f8f9 100644 --- a/compiler/parser/parser.odin +++ b/compiler/parser/parser.odin @@ -41,7 +41,7 @@ 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, name: symbol.Id) -> bool { +package_hidden_name :: proc(parser: ^Parser, name: symbol.Id) -> bool { for hidden in parser.hidden_names { if hidden == name { return true @@ -435,7 +435,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.symbol), + !symbol.is_valid(qualifier) && package_hidden_name(parser, name.symbol), ) if token_text(parser, name) == "struct_type" && current(parser).kind == .Bang && peek(parser).kind == .Left_Paren { @@ -2559,7 +2559,7 @@ parse_while :: proc(parser: ^Parser) -> ast.Stmt_Id { return id } -parse_function :: proc(parser: ^Parser, name: token.Token, c_abi, file_hidden: bool) { +parse_function :: proc(parser: ^Parser, name: token.Token, c_abi, package_hidden: bool) { advance(parser) if _, ok := allow(parser, .Left_Paren); !ok { source.add(parser.diagnostics, current(parser).span, "expected '(' after 'func'") @@ -2590,7 +2590,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi, file_hidden: b pkg=parser.pkg, file=parser.file, c_abi=c_abi, - file_hidden=file_hidden, + package_hidden=package_hidden, has_body=false, variadic=variadic, params=params, @@ -2609,7 +2609,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi, file_hidden: b pkg=parser.pkg, file=parser.file, c_abi=c_abi, - file_hidden=file_hidden, + package_hidden=package_hidden, has_body=true, variadic=variadic, params=params, @@ -2795,9 +2795,9 @@ parse_inline_union_type :: proc(parser: ^Parser) -> types.Type { return types.union_anonymous(&parser.module.type_store, fields[:], tag) } -parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout, file_hidden: bool, is_union := false) { +parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout, package_hidden: bool, is_union := false) { start := advance(parser) - id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden) + id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), package_hidden=package_hidden) // 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 @@ -2831,7 +2831,7 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout, file_hidden: "native union declarations require a body" if is_union else "native struct declarations require a body", ) } - if !types.define_record(&parser.module.type_store, id, nil, c_layout, true, is_union, tag=tag, declared_tag=declared_tag) { + if !types.define_record(&parser.module.type_store, id, nil, c_layout, true, is_union, tag=tag, declared_tag=declared_tag, package_hidden=package_hidden) { source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name)) } if !ended_by_newline { @@ -2868,7 +2868,7 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout, file_hidden: if is_union && (inferred_tag || types.is_valid(declared_tag)) { tag = synthesize_union_tag(parser, fields[:]) } - if !types.define_record(&parser.module.type_store, id, fields[:], c_layout, false, is_union, tag=tag, declared_tag=declared_tag, tuple=tuple) { + if !types.define_record(&parser.module.type_store, id, fields[:], c_layout, false, is_union, tag=tag, declared_tag=declared_tag, tuple=tuple, package_hidden=package_hidden) { source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name)) } else if !c_layout && !is_union && !tuple { for value, index in defaults { @@ -2887,10 +2887,10 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout, file_hidden: _ = finish_statement(parser) } -parse_opaque :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) { +parse_opaque :: proc(parser: ^Parser, name: token.Token, package_hidden: bool) { start := advance(parser) - id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden) - if !types.define_record(&parser.module.type_store, id, nil, false, true, false) { + id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), package_hidden=package_hidden) + if !types.define_record(&parser.module.type_store, id, nil, false, true, false, package_hidden=package_hidden) { source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name)) } if current(parser).kind == .Left_Brace { @@ -2915,11 +2915,11 @@ synthesize_union_tag :: proc(parser: ^Parser, fields: []types.Field) -> types.Ty return types.enum_anonymous(&parser.module.type_store, members, types.U16) } -parse_distinct :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) { +parse_distinct :: proc(parser: ^Parser, name: token.Token, package_hidden: bool) { start := advance(parser) child := parse_type(parser) - id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden) - if !types.define_distinct(&parser.module.type_store, id, child) { + id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), package_hidden=package_hidden) + if !types.define_distinct(&parser.module.type_store, id, child, package_hidden) { source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name)) } if !types.is_valid(child) { @@ -2928,7 +2928,7 @@ parse_distinct :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) { _ = finish_statement(parser) } -parse_alias :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) { +parse_alias :: proc(parser: ^Parser, name: token.Token, package_hidden: bool) { start := advance(parser) saved := parser.cursor if current(parser).kind == .Identifier && peek(parser).kind == .Dot { @@ -2945,7 +2945,7 @@ parse_alias :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) { pkg=parser.pkg, file=parser.file, target_pkg=ast.INVALID_PACKAGE, - file_hidden=file_hidden, + package_hidden=package_hidden, valid=true, diagnostic=source.INVALID_DIAGNOSTIC, }) @@ -2956,8 +2956,8 @@ parse_alias :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) { } parser.cursor = saved child := parse_type(parser) - id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden) - if !types.define_alias(&parser.module.type_store, id, child) { + id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), package_hidden=package_hidden) + if !types.define_alias(&parser.module.type_store, id, child, package_hidden) { source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name)) } if !types.is_valid(child) { @@ -3100,7 +3100,7 @@ parse_inline_enum_type :: proc(parser: ^Parser) -> types.Type { return types.enum_anonymous(&parser.module.type_store, members[:], backing) } -parse_enum :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) { +parse_enum :: proc(parser: ^Parser, name: token.Token, package_hidden: bool) { start := advance(parser) explicit_backing := false backing := types.INVALID @@ -3122,8 +3122,8 @@ parse_enum :: proc(parser: ^Parser, name: token.Token, file_hidden: bool) { _ = finish_statement(parser) return } - id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), file_hidden=file_hidden) - if !types.define_enum(&parser.module.type_store, id, backing, members[:], explicit_backing) { + id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol), file=u32(parser.file), package_hidden=package_hidden) + if !types.define_enum(&parser.module.type_store, id, backing, members[:], explicit_backing, package_hidden) { source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name)) } if explicit_backing && deferred { @@ -3252,9 +3252,9 @@ parse_test :: proc(parser: ^Parser, name: token.Token) { } parse_top_level :: proc(parser: ^Parser) { - hide_token, file_hidden := allow(parser, .Keyword_Hide) + hide_token, package_hidden := allow(parser, .Keyword_Hide) if current(parser).kind == .Keyword_Test && peek(parser).kind == .Keyword_Import { - if file_hidden { + if package_hidden { source.add(parser.diagnostics, hide_token.span, "test imports cannot use 'hide'") } start := advance(parser) @@ -3263,7 +3263,7 @@ parse_top_level :: proc(parser: ^Parser) { return } if current(parser).kind == .Keyword_Import { - if file_hidden { + if package_hidden { source.add(parser.diagnostics, hide_token.span, "imports are already file-local and cannot use 'hide'") } start := advance(parser) @@ -3271,7 +3271,7 @@ parse_top_level :: proc(parser: ^Parser) { return } if current(parser).kind != .Identifier { - message := "expected a declaration name after 'hide'" if file_hidden else "expected a top-level declaration" + message := "expected a declaration name after 'hide'" if package_hidden else "expected a top-level declaration" source.add(parser.diagnostics, current(parser).span, message) for current(parser).kind != .Newline && current(parser).kind != .Eof { advance(parser) @@ -3281,7 +3281,7 @@ parse_top_level :: proc(parser: ^Parser) { } name := advance(parser) if current(parser).kind == .Keyword_Test { - if file_hidden { + if package_hidden { source.add(parser.diagnostics, hide_token.span, "test declarations cannot use 'hide'") } parse_test(parser, name) @@ -3292,7 +3292,7 @@ parse_top_level :: proc(parser: ^Parser) { advance(parser) skip_newlines(parser) if current(parser).kind == .Keyword_Import { - if file_hidden { + if package_hidden { source.add(parser.diagnostics, hide_token.span, "imports are already file-local and cannot use 'hide'") } start := advance(parser) @@ -3303,7 +3303,7 @@ parse_top_level :: proc(parser: ^Parser) { } if current(parser).kind == .Keyword_Func || current(parser).kind == .Keyword_C_Func { c_abi := current(parser).kind == .Keyword_C_Func - parse_function(parser, name, c_abi, file_hidden) + parse_function(parser, name, c_abi, package_hidden) return } type_syntax := types.INVALID @@ -3324,32 +3324,32 @@ parse_top_level :: proc(parser: ^Parser) { source.add(parser.diagnostics, span_from(name.span, current(parser).span), "function declarations do not use '::'; write 'name func(...)' or 'name c_func(...)'") c_abi := current(parser).kind == .Keyword_C_Func - parse_function(parser, name, c_abi, file_hidden) + parse_function(parser, name, c_abi, package_hidden) return } if operator.kind == .Colon_Colon && (current(parser).kind == .Keyword_Struct || current(parser).kind == .Keyword_C_Struct) { - parse_struct(parser, name, current(parser).kind == .Keyword_C_Struct, file_hidden) + parse_struct(parser, name, current(parser).kind == .Keyword_C_Struct, package_hidden) return } if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Opaque { - parse_opaque(parser, name, file_hidden) + parse_opaque(parser, name, package_hidden) return } if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Union { - parse_struct(parser, name, false, file_hidden, is_union=true) + parse_struct(parser, name, false, package_hidden, is_union=true) return } if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Enum { - parse_enum(parser, name, file_hidden) + parse_enum(parser, name, package_hidden) return } if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Distinct { - parse_distinct(parser, name, file_hidden) + parse_distinct(parser, name, package_hidden) return } if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Alias { - parse_alias(parser, name, file_hidden) + parse_alias(parser, name, package_hidden) return } @@ -3360,7 +3360,7 @@ parse_top_level :: proc(parser: ^Parser) { name=name.symbol, pkg=parser.pkg, file=parser.file, - file_hidden=file_hidden, + package_hidden=package_hidden, type=type_syntax, immutable=operator.kind == .Colon_Colon, expr=expr, diff --git a/compiler/testing.odin b/compiler/testing.odin index 6ffdbbd..3fc56f2 100644 --- a/compiler/testing.odin +++ b/compiler/testing.odin @@ -143,6 +143,7 @@ inject_assertion_locations :: proc( continue } location := location_expr(module, sources, symbols, expr.span, expr.qualifier) + expr = &module.exprs[index] args := make([]ast.Expr_Id, len(expr.args)+1, module.allocator) copy(args, expr.args) args[len(expr.args)] = location diff --git a/compiler/types/types.odin b/compiler/types/types.odin index 63b828c..d51be22 100644 --- a/compiler/types/types.odin +++ b/compiler/types/types.odin @@ -109,7 +109,7 @@ Node :: struct { tuple: bool, opaque: bool, declared: bool, - file_hidden: bool, + package_hidden: bool, explicit_backing: bool, } @@ -193,43 +193,38 @@ intern :: proc(store: ^Store, candidate: Node) -> Type { return id } -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) +named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff, package_hidden := false) -> Type { + normalized_file := file if qualifier != 0 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 && - (visibility_matches || qualifier == 0 && existing.file_hidden != file_hidden) { + (qualifier == 0 || existing.file == normalized_file) { return DYNAMIC_START+Type(index) } } - return intern(store, Node{kind=.Named, pkg=pkg, name=name, qualifier=qualifier, file=normalized_file, file_hidden=file_hidden}) + return intern(store, Node{ + kind=.Named, + pkg=pkg, + name=name, + qualifier=qualifier, + file=normalized_file, + }) } 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.file_hidden { + existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier && + (!existing.package_hidden || file != 0xffff_ffff) { return DYNAMIC_START+Type(index) } } return INVALID } -define_alias :: proc(store: ^Store, id, child: Type) -> bool { +define_alias :: proc(store: ^Store, id, child: Type, package_hidden := false) -> bool { existing, ok := node(store, id) if !ok || existing.kind != .Named || existing.declared { return false @@ -237,11 +232,12 @@ define_alias :: proc(store: ^Store, id, child: Type) -> bool { index := int(id-DYNAMIC_START) store.nodes[index].kind = .Alias store.nodes[index].child = child + store.nodes[index].package_hidden = package_hidden store.nodes[index].declared = true return true } -define_distinct :: proc(store: ^Store, id, child: Type) -> bool { +define_distinct :: proc(store: ^Store, id, child: Type, package_hidden := false) -> bool { existing, ok := node(store, id) if !ok || existing.kind != .Named || existing.declared { return false @@ -249,11 +245,12 @@ define_distinct :: proc(store: ^Store, id, child: Type) -> bool { index := int(id-DYNAMIC_START) store.nodes[index].kind = .Distinct store.nodes[index].child = child + store.nodes[index].package_hidden = package_hidden store.nodes[index].declared = true return true } -define_enum :: proc(store: ^Store, id, backing: Type, members: []Enum_Member, explicit_backing: bool) -> bool { +define_enum :: proc(store: ^Store, id, backing: Type, members: []Enum_Member, explicit_backing: bool, package_hidden := false) -> bool { existing, ok := node(store, id) if !ok || existing.kind != .Named || existing.declared { return false @@ -264,6 +261,7 @@ define_enum :: proc(store: ^Store, id, backing: Type, members: []Enum_Member, ex store.nodes[index].field_start = u32(len(store.enum_members)) store.nodes[index].field_count = u32(len(members)) store.nodes[index].explicit_backing = explicit_backing + store.nodes[index].package_hidden = package_hidden store.nodes[index].declared = true append(&store.enum_members, ..members) return true @@ -280,6 +278,7 @@ define_record :: proc( tag: Type = INVALID, declared_tag: Type = INVALID, tuple := false, + package_hidden := false, ) -> bool { existing, ok := node(store, id) if !ok || (existing.kind != .Named && existing.kind != .Struct && existing.kind != .Union) || @@ -291,6 +290,7 @@ define_record :: proc( store.nodes[index].c_layout = c_layout store.nodes[index].tuple = tuple store.nodes[index].opaque = opaque + store.nodes[index].package_hidden = package_hidden store.nodes[index].declared = true store.nodes[index].explicit_size = explicit_size store.nodes[index].explicit_alignment = explicit_alignment diff --git a/compiler_tests.odin b/compiler_tests.odin index f261c8c..03cae82 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -9752,7 +9752,7 @@ hide_is_rejected_outside_named_top_level_declarations :: proc(t: ^testing.T) { } @(test) -hide_declarations_are_file_hidden :: proc(t: ^testing.T) { +hide_declarations_are_package_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) @@ -9762,7 +9762,7 @@ hide_declarations_are_file_hidden :: proc(t: ^testing.T) { } @(test) -file_hidden_declarations_are_not_package_members :: proc(t: ^testing.T) { +package_hidden_declarations_resolve_locally_but_not_through_imports :: proc(t: ^testing.T) { sources := source.init_store() defer source.destroy_store(&sources) diagnostics := source.init_store_diagnostics(&sources) @@ -9787,9 +9787,9 @@ file_hidden_declarations_are_not_package_members :: proc(t: ^testing.T) { 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_sibling) + testing.expect(t, !found_sibling_value) + testing.expect(t, !found_sibling_type) testing.expect(t, found_import) testing.expect(t, found_collision) } @@ -9910,13 +9910,15 @@ RenamedBox :: alias dep.Box RenamedPoint :: alias dep.Point counter :: alias dep.counter answer :: alias dep.answer -hide local_answer_alias :: alias dep.answer local_answer func() i32 { return local_answer_alias() } Scalar :: alias i32 ScalarChain :: alias Scalar static_scalar Scalar :: Scalar(usize(6)) MaybePoint :: alias ?@dep.Point Concrete :: alias dep.Box(i32) +` + hidden_text := `dep :: import "../dep" +hide local_answer_alias :: alias dep.answer ` top_text := `facade :: import "../facade" Box :: alias facade.RenamedBox @@ -9946,6 +9948,7 @@ main func() i32 { ` testing.expect(t, os.write_entire_file(dep_dir + "/dep.bro", transmute([]byte)dep_text)) testing.expect(t, os.write_entire_file(facade_dir + "/facade.bro", transmute([]byte)facade_text)) + testing.expect(t, os.write_entire_file(facade_dir + "/hidden.bro", transmute([]byte)hidden_text)) testing.expect(t, os.write_entire_file(top_dir + "/top.bro", transmute([]byte)top_text)) testing.expect(t, os.write_entire_file(app_dir + "/main.bro", transmute([]byte)app_text)) @@ -10083,7 +10086,7 @@ main func() void {} testing.expect(t, loaded) wants := []string{ "has no member 'missing'", - "is file-hidden", + "is package-hidden", "unknown symbol 'nope'", "unavailable imported package 'gone'", "package member 'dep.ambiguous' is ambiguous", @@ -14961,6 +14964,8 @@ enum_field_struct_and_contextual_anonymous_records_compile_and_run :: proc(t: ^t text := `meta :: import "@std/meta" testing :: import "@std/testing" +OtherTokenKind :: enum { alpha, beta, end } + TokenKind :: enum(u8) { ident = 3 int = 8 @@ -15034,6 +15039,8 @@ get func($E, $V type, map @Map(E, V), key E) ?V { } main func() i32 { + other Map(OtherTokenKind, []u8) = init(OtherTokenKind, []u8, {alpha = "other"}) + if !other.present[0] { return 26 } _ = ordered if none != 41 { return 24 } if !$(static_none == null) or !$(null == static_none) or @@ -15570,3 +15577,73 @@ main func() void { testing.expect(t, found_missing) testing.expect(t, found_empty_c) } + +@(test) +type_factory_inference_prefers_unique_provenance_before_convertible_direct_evidence :: proc(t: ^testing.T) { + directory := "/tmp/brolang-test-factory-provenance-conversion" + main_path := "/tmp/brolang-test-factory-provenance-conversion/main.bro" + output := "/tmp/brolang-test-factory-provenance-conversion-output" + text := `HashBox func($K, $V type, $hash func(value K) usize) type { + return struct { value V } +} + +string_hash func(value []u8) usize { return value.len } + +StringBox func($V type) type { + return HashBox([]u8, V, string_hash) +} + +put func( + $K, $V type, + $hash func(value K) usize, + box @mut HashBox(K, V, hash), + key K, + value V, +) void { + _ = box + _ = key + _ = value +} + +main func() i32 { + box StringBox(u32) = {value = 0} + key []mut u8 = undefined + put(&box, key, 42) + return 0 +} +` + _ = os2.remove_all(directory) + defer _ = os2.remove_all(directory) + defer _ = os.remove(output) + testing.expect(t, os.make_directory(directory) == nil) + testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text)) + testing.expect_value(t, compiler_core.compile_package( + directory, output, nil, target.DEFAULT, cimport.Options{}, ".", + ), 0) + state := run_executable(output) + testing.expect_value(t, state.exit_code, 0) +} + +@(test) +assertion_location_injection_survives_expression_store_growth :: proc(t: ^testing.T) { + directory := "/tmp/brolang-test-assertion-location-growth" + main_path := "/tmp/brolang-test-assertion-location-growth/main.bro" + output := "/tmp/brolang-test-assertion-location-growth-output" + builder := strings.builder_make() + defer strings.builder_destroy(&builder) + strings.write_string(&builder, "testing :: import \"@std/testing\"\n\nmany test {\n") + for index in 0..<128 { + fmt.sbprintf(&builder, "\ttry testing.expect_equal(%d, %d)\n", index, index) + } + strings.write_string(&builder, "}\n") + _ = os2.remove_all(directory) + defer _ = os2.remove_all(directory) + defer _ = os.remove(output) + testing.expect(t, os.make_directory(directory) == nil) + testing.expect(t, os.write_entire_file(main_path, transmute([]byte)strings.to_string(builder))) + testing.expect_value(t, compiler_core.compile_package( + directory, output, nil, target.DEFAULT, cimport.Options{}, ".", .Test, + ), 0) + state := run_executable(output) + testing.expect_value(t, state.exit_code, 0) +} diff --git a/examples/packages/hidden_valid/app/b.bro b/examples/packages/hidden_valid/app/b.bro index d45da22..e3ab4e9 100644 --- a/examples/packages/hidden_valid/app/b.bro +++ b/examples/packages/hidden_valid/app/b.bro @@ -1,36 +1,5 @@ import "../dep" -hide Thing :: struct { - value i32 -} - -hide Local_Union :: union { - value i32 -} - -hide Local_Enum :: enum { - value -} - -hide Local_Opaque :: opaque -hide Local_Distinct :: distinct i32 -hide Local_Alias :: alias i32 - -hide value :: 2 -hide mutable_value i32 = 2 - -hide helper func() i32 { - thing Thing = Thing { value = value } - return thing.value -} - -hide local_foreign c_func() i32 { - return 1 -} - -hide Local_C_Record :: c_struct { - value c_int -} Box :: struct { _value i32 @@ -39,5 +8,6 @@ Box :: struct { 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) + dep._visible() + thing Thing = Thing { value = value } + return helper() + thing.value + _local._value + _foreign() + i32(record.value) + dep._visible() }