@@ -208,6 +208,15 @@ pub const Lowering = struct {
/// `CAllocator` behind a namespace edge from `main`, so the user-visibility
/// `CAllocator` behind a namespace edge from `main`, so the user-visibility
/// gate would reject it) — so the bare TYPE leaf falls open here (F1).
/// gate would reject it) — so the bare TYPE leaf falls open here (F1).
emitting_default_context : bool = false ,
emitting_default_context : bool = false ,
/// Names declared as a BLOCK-LOCAL type (a `Foo :: struct/enum/union/error_set`
/// or bare type-decl statement inside a fn / init body). A local type registers
/// into the global type table and CLOBBERS a same-name top-level entry
/// (`registerStructDecl`'s `findByName … orelse intern` + `updatePreservingKey`),
/// so after it lowers the name IS the local type program-wide (single-author,
/// pre-E2). The source-aware bare-TYPE gate consults this so a legitimately
/// block-local type is never mistaken for a namespaced-only leak — even when a
/// namespaced-only import happens to author a top-level type of the same name.
local_type_names : std . StringHashMap ( void ) = std . StringHashMap ( void ) . init ( std . heap . page_allocator ) ,
struct_defaults_map : std . StringHashMap ( [ ] const ? * const Node ) = std . StringHashMap ( [ ] const ? * const Node ) . init ( std . heap . page_allocator ) , // struct name → field defaults
struct_defaults_map : std . StringHashMap ( [ ] const ? * const Node ) = std . StringHashMap ( [ ] const ? * const Node ) . init ( std . heap . page_allocator ) , // struct name → field defaults
struct_instance_bindings : std . StringHashMap ( std . StringHashMap ( TypeId ) ) = std . StringHashMap ( std . StringHashMap ( TypeId ) ) . init ( std . heap . page_allocator ) , // mangled struct name → type param bindings
struct_instance_bindings : std . StringHashMap ( std . StringHashMap ( TypeId ) ) = std . StringHashMap ( std . StringHashMap ( TypeId ) ) . init ( std . heap . page_allocator ) , // mangled struct name → type param bindings
struct_instance_template : std . StringHashMap ( [ ] const u8 ) = std . StringHashMap ( [ ] const u8 ) . init ( std . heap . page_allocator ) , // mangled struct name → template name
struct_instance_template : std . StringHashMap ( [ ] const u8 ) = std . StringHashMap ( [ ] const u8 ) . init ( std . heap . page_allocator ) , // mangled struct name → template name
@@ -1779,46 +1788,63 @@ pub const Lowering = struct {
if ( name . len > 0 and ( name [ 0 ] = = '[' or name [ 0 ] = = '*' or name [ 0 ] = = '?' ) ) {
if ( name . len > 0 and ( name [ 0 ] = = '[' or name [ 0 ] = = '*' or name [ 0 ] = = '?' ) ) {
return . { . resolved = self . typeResolver ( ) . resolveName ( name , raw ) } ;
return . { . resolved = self . typeResolver ( ) . resolveName ( name , raw ) } ;
}
}
// Registered named type — gated on BARE-FLAT visibility (F1, the type
// Bare nominal name: select its author over the ONE graph-walk collector
// analog of Phase B's value/function tightening). A namespaced-only typ e
// (`typeBareVisibleAsType` — flat-import reachable TYPE author) plus th e
// is registered GLOBALLY yet is reachable from the querying module only
// single-hop collector for the alias path. A bare TYPE name is visible iff
// over a namespace edge, so without this gate its bare reference leaked
// a flat-import-reachable module authors it AS A TYPE (R1: a same-name
// through the global `findByName` first-match. The gate is the TRANSITIVE
// VALUE/FUNCTION does not count); a namespaced-only TYPE is registered
// flat-import reachability `typeBareVisible` — NOT `collectVisibleAuthors`,
// GLOBALLY yet reachable only over a namespace edge, so without this gate
// which walks each module's OWN decls single-hop and would false-negative
// its bare reference leaked through `findByName`'s global first-match.
// a type two flat hops away (e.g. `CAllocator`, reached `main → std.sx →
//
// allocators.sx` over two flat edges). Single-author (E1): the uniqu e
// The TYPE reachability here is the TRANSITIVE flat-import closure, NOT th e
// `findByName` match IS the one bare-v isible a uthor's TypeId, so a
// single-hop `collectV isibleA uthors`/`isNameVisible` set the bare VALUE /
// bare-visible name resolves byte-identically; E2 routes this through the
// FUNCTION / CONST leaves use. That asymmetry (types transitive, values
// collector-selected author's per-source `nominal_id` once same-name type
// non-transitive — 0706) is the open model-consistency question (R3, E1):
// shadows register.
// the value/function model needs the source pin for a library template's
// INTERNAL type refs (`List.append`'s `alloc: Allocator`, instantiated in
// the caller's source context) before the type gate can go single-hop too
// — see the worker report. Until that lands, the transitive type closure
// is the only byte-identical option; the gate stays type-author-aware and
// local-safe regardless of which reachability E1.x settles on.
const name_id = table . internString ( name ) ;
const name_id = table . internString ( name ) ;
if ( table . findByName ( name_id ) ) | existing | {
const registered = table . findByName ( name_id ) ;
// Registered named type (struct/enum/union/error_set/protocol/foreign
// class) — gated on flat-import TYPE visibility (F1, the type analog of
// Phase B's value/function tightening).
if ( registered ) | existing | {
// Compiler-synthesized default-Context emission resolves the built-in
// Compiler-synthesized default-Context emission resolves the built-in
// allocator types as infrastructure — fall open (the gate is for
// allocator types as infrastructure — fall open (the gate is for USER
// USER bare references, not compiler internals).
// bare references, not compiler internals).
if ( self . emitting_default_context ) return . { . resolved = existing } ;
if ( self . emitting_default_context ) return . { . resolved = existing } ;
// The gate applies ONLY to a TOP-LEVEL type author — a `name` declared
// A flat-import-reachable TYPE author makes the bare reference visible.
// in some module's raw facts (`module_decls`). A LOCAL type (declared
// The author must be a TYPE — a same-name flat VALUE/FUNCTION does NOT
// inside a fn / init block), a generic type-param, and a fabricated
// make a namespaced-only type bare-visible (R1). Single-author (E1): the
// empty-struct stub are all findByName-registered yet authored in NO
// unique `findByName` match IS that author's TypeId, so a bare-visible
// `module_decls`; they are not bare cross-module references, so they
// name resolves byte-identically; E2 routes this through the collector-
// resolve ungated and byte-identically (their own diagnostics —
// selected author's per-source `nominal_id` once same-name type shadows
// unknown-type / value-param — still fire in the dedicated pass) .
// register .
if ( self . nameAuthoredAnywhere ( name ) ) {
if ( self . typeBareVisibleAsType ( name , from ) ) return . { . resolved = existing } ;
if ( self . typeBareVisible ( name , from ) ) return . { . resolved = existing } ;
// A block-local type (declared inside a fn / init body) clobbers the
// Registered top-level type reachable ONLY through a namespaced
// global entry for its name, so `existing` IS that local type — never a
// import: a named type is never a `const`, so the alias path
// namespaced-only leak. Resolve it ungated (R2): a legitimately-scoped
// cannot apply — return `.not_visible` so the leaf does not leak
// local must not be rejected just because a namespaced-only import also
// the global match; `resolveNominalLeaf` surfaces the diagnostic
// authors a top-level type of the same name.
// and the `.unresolved` sentinel (qualify it `ns.Type`, Phase F).
if ( self . local_type_names . contains ( name ) ) return . { . resolved = existing } ;
return . not_visible ;
// Registered as a TOP-LEVEL named TYPE in some module, but NOT flat-
}
// import-reachable from `from` and NOT shadowed by a local → reachable
// only over a namespace edge → leak. Return `.not_visible`;
// `resolveNominalLeaf` surfaces the diagnostic and the `.unresolved`
// sentinel (qualify it `ns.Type`, Phase F).
if ( self . nameAuthoredAsTypeAnywhere ( name ) ) return . not_visible ;
// Not a top-level type author anywhere — a generic type-param's bound
// or a fabricated empty-struct stub. Not a bare cross-module reference;
// resolve ungated (its own diagnostics still fire in the dedicated pass).
return . { . resolved = existing } ;
return . { . resolved = existing } ;
}
}
// Type alias `A :: B`. Select the alias author over the ONE graph-walk
// Type alias `A :: B`. Select the alias author over the single-hop
// collector and read its target from the source-keyed cache, keyed by
// collector and read its target from the source-keyed cache, keyed by the
// the author's OWN declaring source (E0's write side) — this is where the
// author's OWN declaring source (E0's write side) — this is where the
// global-alias-leak (0104-F2) fix begins, replacing the global
// global-alias-leak (0104-F2) fix begins, replacing the global
// `type_alias_map` first-match for a flat-visible alias.
// `type_alias_map` first-match for a flat-visible alias.
var res = self . resolver ( ) ;
var res = self . resolver ( ) ;
@@ -1844,23 +1870,45 @@ pub const Lowering = struct {
return null ;
return null ;
}
}
/// TRUE iff bare `name` is reachable from `from` over the TRANSITIVE
/// TRUE iff `raw` declares a NAMED TYPE — struct / enum / union / error-set /
/// flat-import closure (own decls ∪ every transitively flat-imported module's
/// protocol / foreign class. A `fn_decl`, a value-or-alias `const_decl`, and a
/// own decls). The correct `.user_bare_flat ` reachability for the TYPE leaf
/// `namespace_decl ` a re NOT named types (a type alias is a `const_decl` whose
/// (F1): a flat import is transitive for resolution — the global decl list a
/// `findByName` lookup fails, so it never reaches the named-type gate; it
/// module lowers against is the FULL transitive flat list — so a type two flat
/// resolves through the alias path keyed by the const author instead).
/// hops away (`CAllocator`, reached `main → std.sx → allocators.sx`) IS
fn isNamedTypeKind ( raw : resolver_mod . RawDeclRef ) bool {
/// bare-visible, while a namespaced-only type (reached solely over a namespace
return switch ( raw ) {
/// edge, never recorded in `flat_import_graph`) is NOT. The single-hop
. struct_decl , . enum_decl , . union_decl , . error_set_decl , . protocol_decl , . foreign_class_decl = > true ,
/// predicates (`isNameVisible` / `collectVisibleAuthors`, own ∪ DIRECT flat
. fn_decl , . const_decl , . namespace_decl = > false ,
/// deps) would false-negate the transitive case. This closure walk lives in
} ;
/// `lower.zig`, NOT `resolver.zig`, so the single-graph-walk invariant (one
}
/// TRUE iff module `path` authors a top-level decl named `name` AS A TYPE
/// (struct/enum/union/error-set/protocol/foreign class). A same-name
/// VALUE/FUNCTION author returns false (R1: the gate is type-author-aware, not
/// name-only) — the per-module leaf of `typeBareVisibleAsType`'s closure walk.
fn moduleAuthorsType ( decls : * imports_mod . ModuleDecls , path : [ ] const u8 , name : [ ] const u8 ) bool {
const m = decls . get ( path ) orelse return false ;
const ref = m . names . get ( name ) orelse return false ;
return isNamedTypeKind ( ref ) ;
}
/// TRUE iff bare `name` is reachable from `from` AS A TYPE over the flat-import
/// closure (own decls ∪ every transitively flat-imported module's own decls).
/// This is the type-author-aware analog of the value/function visibility, but
/// TRANSITIVE rather than single-hop — the open R3 asymmetry (see the gate
/// comment in `selectNominalLeaf`): a library template's INTERNAL type ref
/// (`List.append`'s `alloc: Allocator`, declared in `std.sx` but instantiated
/// in the caller's source context) is two flat hops from the caller, and the
/// value/function source-pin that would let the type gate go single-hop too is
/// not yet wired for generic instantiation. A same-name flat VALUE/FUNCTION does
/// NOT make the name type-visible (R1). The closure walk lives in `lower.zig`,
/// NOT `resolver.zig`, so the single-graph-walk invariant (one
/// `flat_import_graph` iterator in `resolver.zig`) is untouched. Falls open
/// `flat_import_graph` iterator in `resolver.zig`) is untouched. Falls open
/// (visible) when the scoping facts are unwired (comptime / registration).
/// (visible) when the scoping facts are unwired (comptime / registration).
fn typeBareVisible ( self : * Lowering , name : [ ] const u8 , from : [ ] const u8 ) bool {
fn typeBareVisibleAsType ( self : * Lowering , name : [ ] const u8 , from : [ ] const u8 ) bool {
const decls = self . program_index . module_decls orelse return true ;
const decls = self . program_index . module_decls orelse return true ;
const graph = self . program_index . flat_import_graph orelse return true ;
const graph = self . program_index . flat_import_graph orelse return true ;
if ( moduleAuthorsNam e ( decls , from , name ) ) return true ;
if ( moduleAuthorsTyp e ( decls , from , name ) ) return true ;
var visited = std . StringHashMap ( void ) . init ( self . alloc ) ;
var visited = std . StringHashMap ( void ) . init ( self . alloc ) ;
defer visited . deinit ( ) ;
defer visited . deinit ( ) ;
var queue = std . ArrayList ( [ ] const u8 ) . empty ;
var queue = std . ArrayList ( [ ] const u8 ) . empty ;
@@ -1875,33 +1923,34 @@ pub const Lowering = struct {
const dep = kv . key_ptr . * ;
const dep = kv . key_ptr . * ;
if ( visited . contains ( dep ) ) continue ;
if ( visited . contains ( dep ) ) continue ;
visited . put ( dep , { } ) catch continue ;
visited . put ( dep , { } ) catch continue ;
if ( moduleAuthorsNam e ( decls , dep , name ) ) return true ;
if ( moduleAuthorsTyp e ( decls , dep , name ) ) return true ;
queue . append ( self . alloc , dep ) catch continue ;
queue . append ( self . alloc , dep ) catch continue ;
}
}
}
}
return false ;
return false ;
}
}
/// TRUE iff module `path` authors a top-level decl named `name` (the Phase A
/// TRUE iff `name` is authored as a TOP-LEVEL NAMED TYPE in ANY module's raw
/// raw-fact membership — own decls only, the per-module leaf of the closur e
/// facts. Distinguishes a real cross-module TYPE author (the only thing th e
/// walk in `typeBareVisible`).
/// bare-flat visibility gate polices) from a LOCAL type / generic-param /
fn moduleAuthorsName ( decls : * imports_mod . ModuleDecls , path : [ ] const u8 , name : [ ] const u8 ) bool {
/// fabricated empty-struct stub (findByName-registered but authored in no
const m = decls . get ( path ) orelse return false ;
/// `module_decls`) and from a same-name VALUE/FUNCTION author (not a type).
return m . names . contains ( name ) ;
/// Unwired facts → false (nothing to gate; resolve ungated).
}
fn nameAuthoredAsTypeAnywhere ( self : * Lowering , name : [ ] const u8 ) bool {
/// TRUE iff `name` is authored as a TOP-LEVEL decl in ANY module's raw facts.
/// Distinguishes a real cross-module type author (the only thing the bare-flat
/// visibility gate polices) from a LOCAL type / generic-param / fabricated
/// empty-struct stub, which are findByName-registered but authored in no
/// `module_decls`. Unwired facts → false (nothing to gate; resolve ungated).
fn nameAuthoredAnywhere ( self : * Lowering , name : [ ] const u8 ) bool {
const decls = self . program_index . module_decls orelse return false ;
const decls = self . program_index . module_decls orelse return false ;
var it = decls . valueIterator ( ) ;
var it = decls . valueIterator ( ) ;
while ( it . next ( ) ) | m | if ( m . names . contains ( name ) ) return true ;
while ( it . next ( ) ) | m | {
if ( m . names . get ( name ) ) | ref | if ( isNamedTypeKind ( ref ) ) return true ;
}
return false ;
return false ;
}
}
/// Record a name declared as a BLOCK-LOCAL type so the bare-TYPE gate never
/// mistakes it for a namespaced-only leak (see `local_type_names`).
fn recordLocalTypeName ( self : * Lowering , name : [ ] const u8 ) void {
self . local_type_names . put ( name , { } ) catch { } ;
}
/// Resolve the bare TYPE leaf to a `TypeId` for `resolveTypeWithBindings`.
/// Resolve the bare TYPE leaf to a `TypeId` for `resolveTypeWithBindings`.
/// Routes through the source-aware `selectNominalLeaf`; `.pending` /
/// Routes through the source-aware `selectNominalLeaf`; `.pending` /
/// `.undeclared` keep the legacy empty-struct stub (E3 turns these into the
/// `.undeclared` keep the legacy empty-struct stub (E3 turns these into the
@@ -2697,11 +2746,18 @@ pub const Lowering = struct {
_ = self . builder . emit ( . { . call = . { . callee = fids . set , . args = restore_args } } , . void ) ;
_ = self . builder . emit ( . { . call = . { . callee = fids . set , . args = restore_args } } , . void ) ;
} ,
} ,
// Block-local type declarations
// Block-local type declarations
. struct_decl = > | sd | self . registerStructDecl ( & sd ) ,
. struct_decl = > | sd | {
self . recordLocalTypeName ( sd . name ) ;
self . registerStructDecl ( & sd ) ;
} ,
. enum_decl , . union_decl = > {
. enum_decl , . union_decl = > {
if ( node . data . declName ( ) ) | dn | self . recordLocalTypeName ( dn ) ;
_ = type_bridge . resolveAstType ( node , & self . module . types , & self . program_index . type_alias_map , & self . program_index . module_const_map ) ;
_ = type_bridge . resolveAstType ( node , & self . module . types , & self . program_index . type_alias_map , & self . program_index . module_const_map ) ;
} ,
} ,
. error_set_decl = > self . registerErrorSetDecl ( node ) ,
. error_set_decl = > {
if ( node . data . declName ( ) ) | dn | self . recordLocalTypeName ( dn ) ;
self . registerErrorSetDecl ( node ) ;
} ,
. ufcs_alias = > | ua | {
. ufcs_alias = > | ua | {
self . program_index . ufcs_alias_map . put ( ua . name , ua . target ) catch { } ;
self . program_index . ufcs_alias_map . put ( ua . name , ua . target ) catch { } ;
} ,
} ,
@@ -2865,10 +2921,12 @@ pub const Lowering = struct {
// Handle local type declarations: MyType :: struct/union/enum { ... }
// Handle local type declarations: MyType :: struct/union/enum { ... }
if ( cd . value . data = = . struct_decl ) {
if ( cd . value . data = = . struct_decl ) {
self . recordLocalTypeName ( cd . name ) ;
self . registerStructDecl ( & cd . value . data . struct_decl ) ;
self . registerStructDecl ( & cd . value . data . struct_decl ) ;
return ;
return ;
}
}
if ( cd . value . data = = . enum_decl or cd . value . data = = . union_decl ) {
if ( cd . value . data = = . enum_decl or cd . value . data = = . union_decl ) {
self . recordLocalTypeName ( cd . name ) ;
_ = type_bridge . resolveAstType ( cd . value , & self . module . types , & self . program_index . type_alias_map , & self . program_index . module_const_map ) ;
_ = type_bridge . resolveAstType ( cd . value , & self . module . types , & self . program_index . type_alias_map , & self . program_index . module_const_map ) ;
return ;
return ;
}
}