@@ -1636,6 +1636,23 @@ pub const Lowering = struct {
materialized : ? FuncId = null ,
} ;
/// Outcome of the source-aware bare TYPE leaf (`selectNominalLeaf`, R5 §E).
/// The type-position analogue of `BareCallee`: the nominal author is selected
/// over the ONE graph-walk collector and resolved against the source-keyed
/// caches, never the global `findByName` first-match / global alias map.
pub const TypeHeadResolution = union ( enum ) {
/// A builtin primitive, a registered named type, or a resolved alias.
resolved : TypeId ,
/// A const author is visible but its alias target is not resolved yet —
/// a forward identifier alias. Routes back into the existing
/// `resolveForwardIdentifierAliases` fixpoint (source-aware in E1.5).
pending ,
/// No flat-visible (own ∪ flat-import) author declares `name` as a type.
/// E1 keeps the existing empty-struct stub; E3 turns this into the
/// `.unresolved` sentinel + a diagnostic.
undeclared ,
} ;
/// THE plain bare-name call selector (fix-0102c, R5 §C). `resolveBareCallee`'s
/// body verbatim, now over the Phase B author collector
/// (`resolver.collectVisibleAuthors` — the ONE graph-walk) instead of a direct
@@ -1709,6 +1726,99 @@ pub const Lowering = struct {
return . { . func = . { . decl = the_one . ? , . source = the_source } } ;
}
/// THE source-aware bare TYPE leaf (R5 §E, E1). The type-position analogue
/// of `selectPlainCallableAuthor`: resolve a bare type name `name` referenced
/// from `from` by selecting its nominal author over the ONE graph-walk
/// collector (`resolver.collectVisibleAuthors`) and reading the alias from the
/// source-keyed cache (`type_aliases_by_source`, E0's write side) keyed by the
/// selected author's OWN source — never the global `findByName` first-match
/// nor the global `type_alias_map`.
///
/// `raw` is the backtick raw-identifier escape (issue 0089): a raw reference
/// bypasses the builtin classifier and resolves only through the nominal
/// author / alias path.
///
/// E1 is single-author: `collectVisibleAuthors` returns ≤1 author, so the
/// selection is unambiguous and resolution is byte-identical to the legacy
/// leaf. Same-name shadows (≥2 authors) and the `.ambiguous` outcome (0105)
/// land in E2; the per-author `nominal_id` TypeId that makes a shadow
/// representable also lands then (today a registered named type resolves to
/// its unique `findByName` match, which IS the single author's TypeId).
/// Generic / parameterized-protocol / Vector / type-function heads never
/// reach this leaf — `resolveTypeWithBindings` owns those above the leaf
/// switch, so they stay legacy.
pub fn selectNominalLeaf ( self : * Lowering , name : [ ] const u8 , from : [ ] const u8 , raw : bool ) TypeHeadResolution {
const table = & self . module . types ;
// Builtin primitive keyword / arbitrary-width int — unless a raw escape
// routes the literal name straight to nominal resolution.
if ( ! raw ) {
if ( TypeResolver . resolveBuiltinName ( name , table ) ) | id | return . { . resolved = id } ;
}
// Structural string-forms that reach the leaf as a literal type-expr
// name (`[:0]u8` → string, `[*]T`, `*T`, `?T`) carry NO nominal author —
// they are wrappers, not declarations, so source-keying does not apply.
// Resolve them through the stateless namer exactly as the legacy leaf
// did; only the bare nominal name below cuts over to the collector.
if ( name . len > 0 and ( name [ 0 ] = = '[' or name [ 0 ] = = '*' or name [ 0 ] = = '?' ) ) {
return . { . resolved = self . typeResolver ( ) . resolveName ( name , raw ) } ;
}
// Registered named type. Single-author (E1): its unique registered
// TypeId. `findByName` stays the byte-identical resolver here — it also
// reaches a namespaced-only type referenced bare (the global leak 0719
// relies on); E2 routes this through the collector-selected author's
// per-source `nominal_id` once same-name type shadows register, and E3
// turns a true miss into the `.unresolved` sentinel + a diagnostic.
const name_id = table . internString ( name ) ;
if ( table . findByName ( name_id ) ) | existing | return . { . resolved = existing } ;
// Type alias `A :: B`. Select the alias author over the ONE graph-walk
// collector and read its target from the source-keyed cache, keyed by
// the 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.
var res = self . resolver ( ) ;
const set = res . collectVisibleAuthors ( name , from , . user_bare_flat ) ;
defer if ( set . flat . len > 0 ) self . alloc . free ( set . flat ) ;
if ( constAuthor ( set ) ) | author | {
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 ;
}
/// The single `const_decl` (alias-or-value const) author of a collected
/// `AuthorSet`. E1 is single-author (`collectVisibleAuthors` returns ≤1), so
/// own-then-flat picks the one author. E2 adds shadow ambiguity.
fn constAuthor ( set : resolver_mod . AuthorSet ) ? resolver_mod . RawAuthor {
if ( set . own ) | o | if ( o . raw = = . const_decl ) return o ;
for ( set . flat ) | fa | if ( fa . raw = = . const_decl ) return fa ;
return null ;
}
/// Resolve the bare TYPE leaf to a `TypeId` for `resolveTypeWithBindings`.
/// Routes through the source-aware `selectNominalLeaf`; `.pending` /
/// `.undeclared` keep the legacy empty-struct stub (E3 turns these into the
/// `.unresolved` sentinel + a diagnostic). When the source context is unwired
/// (`current_source_file` null — comptime / registration callers), there is
/// no querying module to collect from, so fall open to the legacy namer.
fn resolveNominalLeaf ( self : * Lowering , name : [ ] const u8 , raw : bool ) TypeId {
const from = self . current_source_file orelse
return self . typeResolver ( ) . resolveName ( name , raw ) ;
return switch ( self . selectNominalLeaf ( name , from , raw ) ) {
. resolved = > | t | t ,
// The legacy empty-struct stub for an as-yet-unregistered / forward
// name — `resolveNamed`'s tail, reproduced for byte-identity. A raw
// or non-raw bare name both land the same struct stub here.
. pending , . undeclared = > self . module . types . intern ( . { . @ " struct " = . {
. name = self . module . types . internString ( name ) ,
. fields = & . { } ,
} } ) ,
} ;
}
/// The `*FnDecl` a raw author wraps, or null when the author is not a
/// function — `imports.fnDeclOf` over a `RawDeclRef` so the collector's
/// all-domain authors reproduce `module_fns`' fn-only view (a `const`-wrapped
@@ -12849,6 +12959,7 @@ pub const Lowering = struct {
/// `evalConstIntExpr` delegation inside `evalConstFloatExpr`; this surfaces the
/// non-integral float const so the rule can reject it.
pub fn lookupFloatName ( self : * Lowering , name : [ ] const u8 ) ? f64 {
if ( self . moduleConstBareInvisible ( name ) ) return null ;
return program_index_mod . moduleConstFloat ( & self . program_index . module_const_map , & self . module . types , name ) ;
}
@@ -12859,6 +12970,7 @@ pub const Lowering = struct {
/// value bindings are always integer-valued, so only the module-const table
/// can name a float.
pub fn nameIsFloatTyped ( self : * Lowering , name : [ ] const u8 ) bool {
if ( self . moduleConstBareInvisible ( name ) ) return false ;
return program_index_mod . moduleConstIsFloatTyped ( & self . program_index . module_const_map , & self . module . types , name ) ;
}
@@ -12871,12 +12983,45 @@ pub const Lowering = struct {
if ( self . comptime_value_bindings ) | cvb | {
if ( cvb . get ( name ) ) | v | return v ;
}
// Folded req #1: gate the bare module const on source-aware visibility
// before reading the global map (see `moduleConstBareInvisible`).
if ( self . moduleConstBareInvisible ( name ) ) return null ;
// The module-const branch is shared verbatim with the stateless
// registration-time resolver (`type_bridge`) so a `[N]T` dimension
// resolves to the same length on both paths (issue 0083).
return program_index_mod . moduleConstInt ( & self . program_index . module_const_map , & self . module . types , name ) ;
}
/// Folded req #1: TRUE iff `name` is a module const that is NOT reachable
/// bare from the querying module — the source-aware gate every Lowering-side
/// comptime `module_const_map` reader (`comptimeIntNamed` / `lookupFloatName`
/// / `nameIsFloatTyped`) consults before the global first-match. A
/// namespaced-only import's const must be qualified (`ns.X`); without this
/// gate a bare reference leaks into a comptime-scalar / array-dim position
/// through the global table (the int folder even falls back to the float
/// reader, so all three must gate). The value itself is still folded over the
/// global map, so a cross-module const CHAIN (`N :: M + 1`, M flat-imported)
/// resolves exactly as before; the stateless `type_bridge` registration path
/// keeps the global reader this step. A main-file body carries a null
/// `current_source_file` (it IS the root), so the querying module is
/// `main_file` there; a fully unwired index (no source at all) falls open.
fn moduleConstBareInvisible ( self : * Lowering , name : [ ] const u8 ) bool {
const from = self . current_source_file orelse self . main_file orelse return false ;
var res = self . resolver ( ) ;
const set = res . collectVisibleAuthors ( name , from , . user_bare_flat ) ;
defer if ( set . flat . len > 0 ) self . alloc . free ( set . flat ) ;
if ( set . own ) | o | if ( self . sourceHasModuleConst ( o . source , name ) ) return false ;
for ( set . flat ) | fa | if ( self . sourceHasModuleConst ( fa . source , name ) ) return false ;
return true ;
}
/// True iff `source`'s per-source const cache declares `name` (E0's
/// `module_consts_by_source` write side).
fn sourceHasModuleConst ( self : * Lowering , source : [ ] const u8 , name : [ ] const u8 ) bool {
const inner = self . program_index . module_consts_by_source . get ( source ) orelse return false ;
return inner . contains ( name ) ;
}
/// Resolve a type node, checking type_bindings first for generic type params.
pub fn resolveTypeWithBindings ( self : * Lowering , node : * const Node ) TypeId {
// Pack-index in a type position: `$<pack>[<lit>]` resolves to the
@@ -12968,14 +13113,15 @@ pub const Lowering = struct {
if ( node . data = = . type_expr and node . data . type_expr . is_generic ) {
return . unresolved ;
}
// Bare type names resolve through TypeResolver, which reads the
// canonical alias table directly (`ProgramIndex.type_alias_map`). Othe r
// node kinds (inline type decls, error types) still route through
// type_bridge, which now takes the alias map as an explicit argument
// (the `TypeTable.aliases` borrow is gone, A2.3).
// Bare type names resolve through the source-aware `selectNominalLeaf`
// (E1): the nominal author is selected over the ONE graph-walk collecto r
// and resolved against the source-keyed caches, not the global
// `findByName` first-match / global alias map. Other node kinds (inline
// type decls, error types) still route through type_bridge, which reads
// the global compat maps (cut over in a later phase).
switch ( node . data ) {
. type_expr = > | te | return self . typeResolver ( ) . resolveName ( te . name , te . is_raw ) ,
. identifier = > | id | return self . typeResolver ( ) . resolveName ( id . name , id . is_raw ) ,
. type_expr = > | te | return self . resolveNominalLeaf ( te . name , te . is_raw ) ,
. identifier = > | id | return self . resolveNominalLeaf ( id . name , id . is_raw ) ,
// A non-spread tuple literal in a type position is a tuple-type
// literal (`(s32, s32)`); validate its elements are types and reject
// non-type elements loudly (issue 0067).