@@ -1788,65 +1788,103 @@ pub const Lowering = struct {
if ( name . len > 0 and ( name [ 0 ] = = '[' or name [ 0 ] = = '*' or name [ 0 ] = = '?' ) ) {
return . { . resolved = self . typeResolver ( ) . resolveName ( name , raw ) } ;
}
// Bare nominal name: select it s author over the ONE graph-walk collector
// (`typeBareVisibleAsType` — flat-import reachable TYPE author) plus the
// single-hop collector for the alias path. A bare TYPE name is visible iff
// a flat-import-reachable module authors it AS A TYPE (R1: a same-name
// VALUE/FUNCTION does not count); a namespaced-only TYPE i s register ed
// GLOBALLY yet reachable only over a namespace edge, so without this gate
// its bare reference leaked through `findByName`'s global first-match.
// Bare nominal name. A bare TYPE name is visible iff a flat-import-
// reachable module authors it AS A TYPE — and a TYPE author is EITHER a
// named type (struct/enum/union/error-set/protocol/foreign class) OR a
// type ALIAS (`Name :: <type>`, a `const_decl` whose value resolved to a
// type, recorded in E0's `type_aliases_by_source`). Both kind s a re gat ed
// identically: `moduleTypeAuthor` is the SINGLE source of truth, so a
// namespaced-only alias leaks no more than a namespaced-only named type,
// and a flat-visible alias is never poisoned by an invisible same-name
// named type (and vice-versa) — R4. A same-name flat VALUE/FUNCTION is
// NOT a type author (R1); a value-const (`N :: 7`) lives in
// `module_consts_by_source`, never in `type_aliases_by_source`, so it is
// correctly excluded too.
//
// The TYPE reachability here is the TRANSITIVE flat-import closure, NOT the
// single-hop `collectVisibleAuthors`/`isNameVisible` set the bare VALUE /
// FUNCTION / CONST leaves use. That asymmetry (types transitive, values
// non-transitive — 0706) is the open model-consistency question (R3, E1):
// 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.
// non-transitive — 0706) is the open model-consistency question (R3,
// sequenced as E4 per Agra): 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 . 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 E4
// settles on.
const name_id = table . internString ( name ) ;
const registered = table . findByName ( name_id ) ;
// Registered named type (struct/enum/union/error_set/protocol/foreig n
// 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
// allocator types as infrastructure — fall open (the gate is for USER
// bare references, not compiler internals).
if ( self . emitting_default_context ) return . { . resolved = existing } ;
// A flat-import-reachable TYPE author makes the bare reference visible.
// The author must be a TYPE — a same-name flat VALUE/FUNCTION does NOT
// make a namespaced-only type bare-visible (R1). Single-author (E1): the
// unique `findByName` match IS that author's TypeId, so a bare-visible
// name resolves byte-identically; E2 routes this through the collector-
// selected author's per-source `nominal_id` once same-name type shadows
// register.
if ( self . typeBareVisibleAsType ( name , from ) ) return . { . resolved = existing } ;
// A block-local type (declared inside a fn / init body) clobbers the
// global entry for its name, so `existing` IS that local type — never a
// namespaced-only leak. Resolve it ungated (R2): a legitimately-scoped
// local must not be rejected just because a namespaced-only import also
// authors a top-level type of the same name.
if ( self . local_type_names . contains ( name ) ) return . { . resolved = existing } ;
// 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 } ;
// Compiler-synthesized default-Context emission resolves the built-i n
// allocator types as infrastructure — fall open (the gate is for USER bare
// references, not compiler internals ).
if ( self . emitting_default_context ) {
if ( registered ) | existing | return . { . resolved = existing } ;
}
// Type alias `A :: B`. Select the alias author over the single-hop
// collector and read it s target from the source-keyed cache, keyed by th e
// author's OWN declaring source (E0's write side) — this is where the
// global-alias-leak (0104-F2) fix begins, replacing the global
// `type_alias_map` first-match for a flat-visible alias.
// Import facts unwired (registration / comptime host with no module_decls
// or flat graph): there is no querying context to gate against — preserv e
// the legacy resolution (registered → existing; else forward-alias /
// undeclared).
if ( self . program_index . module_decls = = null or self . program_index . flat_import_graph = = null ) {
if ( registered ) | existing | return . { . resolved = existing } ;
return self . forwardAliasOrUndeclared ( name , from ) ;
}
// 1. A flat-import-visible TYPE author (named type OR alias) — resolve to
// its declared TypeId. Single-author (E1): at most one author across
// own ∪ flat closure, so this is byte-identical to the legacy leaf.
// The author KIND decides resolution, decoupled from `findByName`
// timing: an ALIAS resolves to its `type_aliases_by_source` target; a
// NAMED type resolves to its `findByName` entry, OR — when that entry
// is not registered yet (a forward / self reference like
// `next: *ArenaChunk` resolved mid-registration) — to the legacy
// empty-struct stub, reconciled by `updatePreservingKey` when the type
// finally registers. E2 routes named resolution through the
// collector-selected author's per-source `nominal_id` once same-name
// type shadows register.
if ( self . flatVisibleTypeAuthor ( name , from ) ) | author | switch ( author ) {
. alias = > | tid | return . { . resolved = tid } ,
. named = > {
if ( registered ) | existing | return . { . resolved = existing } ;
return . undeclared ;
} ,
} ;
// 2. A block-local type (declared inside a fn / init body) clobbers the
// global entry for its name, so `existing` IS that local type — never a
// namespaced-only leak. Resolve it ungated (R2): a legitimately-scoped
// local must not be rejected just because a namespaced-only import also
// authors a top-level type of the same name.
if ( self . local_type_names . contains ( name ) ) {
if ( registered ) | existing | return . { . resolved = existing } ;
}
// 3. Authored as a TYPE (named OR alias) 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 ;
// 4. Not a cross-module type author. A registered generic type-param bound
// or fabricated empty-struct stub (findByName hit, no module_decls
// author) resolves ungated. Otherwise a forward identifier alias
// (visible const author, target not resolved yet → `.pending`, back to
// the fixpoint) or `.undeclared`.
if ( registered ) | existing | return . { . resolved = existing } ;
return self . forwardAliasOrUndeclared ( name , from ) ;
}
/// The forward-alias / undeclared tail of `selectNominalLeaf`: a bare nominal
/// name that is neither a flat-visible type author, a local, nor a leak.
/// Selects the single-hop const author (E1: `collectVisibleAuthors` returns
/// ≤1) and, if its alias target is not yet in `type_aliases_by_source`,
/// returns `.pending` so the forward-alias fixpoint re-resolves it (source-
/// aware in E1.5). A resolved flat-visible alias is already returned by
/// `flatVisibleTypeAuthor` above, so the `inner.get` here only catches a
/// const author reachable via `collectVisibleAuthors` whose target landed
/// between the two reads — the fixpoint path is the common outcome.
fn forwardAliasOrUndeclared ( self : * Lowering , name : [ ] const u8 , from : [ ] const u8 ) TypeHeadResolution {
var res = self . resolver ( ) ;
const set = res . collectVisibleAuthors ( name , from , . user_bare_flat ) ;
defer if ( set . flat . len > 0 ) self . alloc . free ( set . flat ) ;
@@ -1854,8 +1892,6 @@ pub const Lowering = struct {
if ( self . program_index . type_aliases_by_source . get ( author . source ) ) | inner | {
if ( inner . get ( name ) ) | alias_ty | return . { . resolved = alias_ty } ;
}
// Const author visible but its alias target is not resolved yet —
// a forward identifier alias → `.pending`, back to the fixpoint.
return . pending ;
}
return . undeclared ;
@@ -1872,9 +1908,10 @@ pub const Lowering = struct {
/// TRUE iff `raw` declares a NAMED TYPE — struct / enum / union / error-set /
/// protocol / foreign class. A `fn_decl`, a value-or-alias `const_decl`, and a
/// `namespace_decl` are NOT named types (a type alias is a `const_decl` whose
/// `findByName` lookup fails, so it never reaches the named-type gate; it
/// resolves through the alias path keyed by the const author instead).
/// `namespace_decl` are NOT named types. A type ALIAS is a `const_decl`; it is
/// recognised as a type author NOT here but via `type_aliases_by_source`
/// (E0's source-keyed cache) in `moduleTypeAuthor`, so the two type-author
/// kinds — named type and alias — gate identically (R4).
fn isNamedTypeKind ( raw : resolver_mod . RawDeclRef ) bool {
return switch ( raw ) {
. struct_decl , . enum_decl , . union_decl , . error_set_decl , . protocol_decl , . foreign_class_decl = > true ,
@@ -1882,39 +1919,64 @@ pub const Lowering = struct {
} ;
}
/// TRUE iff module `path` authors a top-level decl named `name` AS A TYPE
/// (struct/enum/union/error-set/protocol/foreign class). A same-nam e
/// VALUE/FUNCTION author returns false (R1: the gate is t ype-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 ) ;
/// A module's authorship of a bare type `name`: an ALIAS (carrying the
/// resolved target `TypeId` from `type_aliases_by_source`) or a NAMED typ e
/// (struct/enum/union/error-set/protocol/foreign class — it s T ypeId is
/// resolved at the use site from `findByName`, decoupled from this predicate
/// so a not-yet-registered forward / self reference is still recognised as an
/// author).
const FlatTypeAuthor = union ( enum ) {
alias : TypeId ,
named ,
} ;
/// How module `path` authors `name` AS A TYPE, or null if it does not. A type
/// author is EITHER a type ALIAS (`Name :: <type>`, recorded in E0's
/// `type_aliases_by_source` — checked first via the source-keyed cache) OR a
/// NAMED type (recognised by its `module_decls` decl KIND, NOT by `findByName`
/// — so a forward / self reference resolved before the type registers is still
/// an author). A same-name VALUE/FUNCTION is NOT a type author (R1); a
/// value-const (`N :: 7`) lives in `module_consts_by_source`, never
/// `type_aliases_by_source`, so it returns null too. THE per-module "is `name`
/// a type author here?" predicate — the single source of truth for the
/// visibility walk (R4).
fn moduleTypeAuthor ( self : * Lowering , path : [ ] const u8 , name : [ ] const u8 ) ? FlatTypeAuthor {
if ( self . program_index . type_aliases_by_source . get ( path ) ) | inner | {
if ( inner . get ( name ) ) | tid | return . { . alias = tid } ;
}
const decls = self . program_index . module_decls orelse return null ;
const m = decls . get ( path ) orelse return null ;
const ref = m . names . get ( name ) orelse return null ;
if ( ! isNamedTypeKind ( ref ) ) return null ;
return . named ;
}
/// 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 i s
/// 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
/// (visible) w hen the scoping facts are unwired (comptime / registration).
fn typeBareVisibleAsType ( self : * Lowering , name : [ ] const u8 , from : [ ] const u8 ) bool {
const decls = self . program_index . module_decls orelse return true ;
const graph = self . program_index . flat_import_graph orelse return true ;
if ( moduleAuthorsType ( decls , from , name ) ) return true ;
/// The flat-import-visible TYPE author for bare `name` from `from`, or null
/// when no flat-reachable module authors `name` as a type. Walk s own decls ∪
/// the TRANSITIVE flat-import closure (every transitively flat-imported
/// module), returning the first author found via `moduleTypeAuthor` (named
/// type OR alias — the single source of truth, R4). Single-author (E1): at
/// most one author across the closure, so the first hit IS the unique author.
///
/// TRANSITIVE ra ther than single-hop is the open R3 asymmetry (type s
/// transitive, values non-transitive — 0706; sequenced as E4 per Agra): 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 t he type gate go single-hop too is not yet wired for generic
/// instantiation. 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. Returns null when the flat graph is unwired
/// (the caller has already special-cased that to the legacy resolution).
fn flatVisibleTypeAuthor ( self : * Lowering , name : [ ] const u8 , from : [ ] const u8 ) ? FlatTypeAuthor {
const graph = self . program_index . flat_import_graph orelse return null ;
if ( self . moduleTypeAuthor ( from , name ) ) | a | return a ;
var visited = std . StringHashMap ( void ) . init ( self . alloc ) ;
defer visited . deinit ( ) ;
var queue = std . ArrayList ( [ ] const u8 ) . empty ;
defer queue . deinit ( self . alloc ) ;
visited . put ( from , { } ) catch return true ;
queue . append ( self . alloc , from ) catch return true ;
visited . put ( from , { } ) catch return null ;
queue . append ( self . alloc , from ) catch return null ;
var i : usize = 0 ;
while ( i < queue . items . len ) : ( i + = 1 ) {
const deps = graph . get ( queue . items [ i ] ) orelse continue ;
@@ -1923,24 +1985,32 @@ pub const Lowering = struct {
const dep = kv . key_ptr . * ;
if ( visited . contains ( dep ) ) continue ;
visited . put ( dep , { } ) catch continue ;
if ( moduleAuthorsType ( decls , dep, name ) ) return true ;
if ( self . moduleType Author ( dep , name ) ) | a | return a ;
queue . append ( self . alloc , dep ) catch continue ;
}
}
return false ;
return null ;
}
/// TRUE iff `name` is authored as a TOP-LEVEL NAMED TYPE 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 /
/// TRUE iff `name` is authored as a TYPE — a NAMED type OR a type ALIAS — in
/// ANY module's raw facts. The leak detector: a name that is a type author
/// somewhere but not flat- visible from the querying module is reachable only
/// over a namespace edge. Both kinds are checked (R4): named types via
/// `module_decls`, aliases via E0's `type_aliases_by_source`. Distinguishes a
/// real cross-module TYPE author from a LOCAL type / generic-param /
/// fabricated empty-struct stub (findByName-registered but authored in no
/// ` module_decls` ) and from a same-name VALUE/FUNCTION author (not a type).
/// Unwired facts → false (nothing to gate; resolve ungated).
/// module) and from a same-name VALUE/FUNCTION author (not a type). Unwired
/// facts → false (nothing to gate; resolve ungated).
fn nameAuthoredAsTypeAnywhere ( self : * Lowering , name : [ ] const u8 ) bool {
const decls = self . program_index . module_decls orelse return false ;
var it = decls . valueIterator ( ) ;
while ( it . next ( ) ) | m | {
if ( m . names . get ( name ) ) | ref | if ( isNamedTypeKind ( ref ) ) return true ;
if ( self . program_index . module_decls ) | decls | {
var it = decls . valueIterator ( ) ;
while ( it . next ( ) ) | m | {
if ( m . names . get ( name ) ) | ref | if ( isNamedTypeKind ( ref ) ) return true ;
}
}
var ait = self . program_index . type_aliases_by_source . valueIterator ( ) ;
while ( ait . next ( ) ) | inner | {
if ( inner . contains ( name ) ) return true ;
}
return false ;
}