diff --git a/docs/compiler.md b/docs/compiler.md index 1334b93eec..5f322ecb09 100644 --- a/docs/compiler.md +++ b/docs/compiler.md @@ -11,7 +11,8 @@ The Wado compiler (`wado-compiler/`) translates `.wado` source into a Wasm compo ``` Source (.wado) → Lex → Parse → Bind (per module, in loader) - → Annotate (Analyze + Resolve + lower TIR) + → Analyze → Resolve + → Annotate (decls, then bodies) → Liveness → Reify (TIR) → Default-purity Check → Synthesis (auto-derives, template, From, serde, pre-CM effect dispatch, CM bindings) → Effect Check → Stores Check @@ -31,7 +32,11 @@ The driver is `compile_after_load` in `src/lib.rs`. | Lex / Parse | AST | `lexer.rs`, `parser.rs`, `token.rs`, `syntax.rs` | | Bind | AST + bindings | `bind.rs` | | Loader | All modules | `loader.rs` | -| Annotate | TIR + facts | `semantics.rs`, `analyze.rs`, `elaborator/` | +| Analyze | `SymbolTable` | `analyze.rs` | +| Resolve | Declarations | `defs.rs`, `resolve.rs` | +| Annotate | `Semantics` | `semantics.rs`, `elaborator/` | +| Liveness | Reachability | `elaborator/liveness.rs` | +| Reify | `TirModule` | `elaborator/reify.rs` | | Default-purity Check | (validation) | `effect_check.rs::check_default_purity` | | Synthesis | TIR (extended) | `synthesis/` | | Effect / Stores | TIR (validated) | `effect_check.rs` | @@ -51,7 +56,7 @@ The driver is `compile_after_load` in `src/lib.rs`. | Unit | Layer | | --------------------------------- | ------------------------------------------------------------------------------------------------------------ | | `Module` (`ast.rs`) | Surface AST. Preserves source-level syntax to support `wado format`. | -| `TirModule` (`tir.rs`) | Typed IR. One per source module after annotate. | +| `TirModule` (`tir.rs`) | Typed IR. One per source module, emitted by reify. | | `Package` (`package.rs`) | Per-module compilation context, used from synthesis through link. | | `FlatPackage` (`flat_package.rs`) | Flat list of all functions, types, and globals; used from monomorphize through the lower pipeline's planner. | | `NirPackage` (`nir_package.rs`) | Normalized IR. Output of lower, input to optimize / WIR build / codegen. | @@ -69,9 +74,16 @@ The loader runs `lexer → parser → bind` on every loaded module: The AST is parser-immutable from this point on. The desugar-replacement surface rewrites — compound assignment (`x += y` → `x = x + y`), `while` / C-style `for` → explicit `loop`, `for x of expr` iteration (the `.into_iter()` / `.next()` dispatch and the `match Some(x) => body, _ => break` shape), the `assert` statement, the `matches` operator, the comparison chain `a < b < c`, template-string interpolations, `use … namespace` prefix stripping (`helper::foo`), and `Self::method` / `T::method` (T bound to concrete) static-call dispatch — happen inside the elaborator and are built TIR-direct: each rewrite resolves the user AST and constructs `TirExpr` / `TirStmt` nodes directly without producing synthetic AST. The implementations live in `elaborator/{stmt,operators,assert,matches}.rs` (`resolve_while`, `resolve_for`, `resolve_iterator_for_of`, `resolve_compound_assign`, `desugar_assert`, `desugar_matches_expr`, `desugar_comparison_chain`), `Elaborator::strip_ns_prefix` in `elaborator.rs`, and `CalleeIdentKind` / `classify_call_callee` in `elaborator/call.rs` (the prefix is resolved to its concrete type name before parameter-type lookup so argument resolution runs once with the correct expected-type hints). Synthetic call sites that need to dispatch a method on an already-resolved receiver TIR (the for-of `.into_iter()` / `.next()` calls today) reuse the AST-driven method dispatch via `Elaborator::resolve_method_call_with` (`elaborator/method_call.rs`) — that helper takes a pre-resolved receiver plus a method name and signals "no source AST" with `method_id: None` so no use→def edge is recorded against the synthesis site. Keeping the AST parser-shaped is what lets LSP queries land on the user's text rather than on a synthesised replacement. -## Annotate (Analyze + Resolve + TIR Lowering) +## Analyze and Elaborate -`semantics_of` (`semantics.rs`) is the entry point shared by LSP and batch compilation. It runs `analyze.rs` for symbol-table construction and `elaborator/` for type checking; bodies are then lowered into TIR. +`semantics_of` (`semantics.rs`) is the entry point shared by LSP and batch compilation. It runs `analyze.rs` for the symbol table, then `elaborator/` for the phases below ([WEP 2026-05-26](./wep-2026-05-26-elaborator-rearchitecture.md)): + +- **Resolve** answers every reference site once, from the module that wrote it, and identifies every declaration (`defs.rs`, `resolve.rs`, [WEP 2026-08-12](./wep-2026-08-12-declaration-identity.md)). Nothing else answers a site; a position that has none derives its `DefId` from a module the caller names. +- **Annotate** runs a declaration pass over every module — types, `TraitEnv`, `Signatures` — then a body walk whose sole output is a populated `ModuleSemantics`. +- **Liveness** computes source-level reachability, which gates what reify emits and feeds the unused diagnostics. +- **Reify** reads the recorded facts back and emits one `TirModule` per module. No inference, no dispatch decisions. + +The LSP path stops after liveness and builds no TIR. The result, `Semantics`, carries the TIR modules plus an `AstIndex` and a use→def map (`AstId → AstId`, globally-unique ids). This is what makes the architecture LSP-friendly: facts are attached to AST nodes without mutating them, so cross-file navigation, hover, and rename all fall out of the same data the batch compiler uses. See the [LSP](#lsp) section below. diff --git a/docs/wep-2026-05-26-elaborator-rearchitecture.md b/docs/wep-2026-05-26-elaborator-rearchitecture.md index e4932d236a..6b0e9b84a4 100644 --- a/docs/wep-2026-05-26-elaborator-rearchitecture.md +++ b/docs/wep-2026-05-26-elaborator-rearchitecture.md @@ -518,7 +518,7 @@ and the surviving suppression is the argument-classification probe. logger, interner, invocations, entry module) behind one `ElabEnv` field, dissolve `AnnotateState` — `tysys` and `module_semantics` land on `Semantics`, the rest are driver locals — and collapse the per-module - construction site. Takes `Elaborator` from 11 fields to 7. + construction site. ## Consequences diff --git a/docs/wep-2026-07-09-local-item-definitions.md b/docs/wep-2026-07-09-local-item-definitions.md index e7acb154cc..857bd26e88 100644 --- a/docs/wep-2026-07-09-local-item-definitions.md +++ b/docs/wep-2026-07-09-local-item-definitions.md @@ -94,6 +94,30 @@ whether reify or this WEP's eager annotate-time emission produced it. generic `type` surfaces as a type mismatch, since it needs a different mechanism — `GenericNewtypeInfo` + AST substitution, not the monomorphized template generic structs use — that this WEP does not wire up. Methods on - any local type surface as `no method 'x' found on type`, since a local - `impl`/`trait` block parses but is not connected to method dispatch, a - module-wide registry built once before any function body is walked. + any local type surface as `no method 'x' found on type`. + +## Known gap: methods on a local type + +Two module-wide registries are built before any function body is walked, and a +local `impl` needs both. + +- `TraitEnv` indexes `module.items`, so no local block reaches it. A body walk + can be added: the target's head has its own reference site, and a block whose + target is a function-local declaration is scoped by that identity, since two + sibling blocks' same-named types are two declarations. +- `Signatures` is assembled between the declaration pass and the body walk and + is read-only after. A local `impl`'s target is a block-local type whose + `TypeId` is interned _during_ that walk, so its method signatures cannot + exist before the freeze. + +Closing it means either giving a local type declaration its durable identity +and `TypeId` in the declaration pass — `declare_local_struct` needs only the +`DefId`, which `DefTable::build` already mints, and sits in the body walk for +_visibility_ — or making `Signatures` extensible after assembly, which gives up +the read-only rule [`wep-2026-05-26`](./wep-2026-05-26-elaborator-rearchitecture.md) +rests on. + +Two things a first attempt hits: the orphan rule reads a function-local target +as foreign, because `type_decl_index` holds module-level declarations only; and +indexing `TraitEnv` alone is _worse_ than the gap, since the header then +resolves where the signature does not and the diagnostic becomes a panic. diff --git a/docs/wep-2026-08-12-declaration-identity.md b/docs/wep-2026-08-12-declaration-identity.md index 5503e0854b..4b8c64fe7b 100644 --- a/docs/wep-2026-08-12-declaration-identity.md +++ b/docs/wep-2026-08-12-declaration-identity.md @@ -467,11 +467,21 @@ a sited entry point a caller with a reference site reaches instead. The same derivation in the `Symbol` currency, which §5's `DefId` columns subsume: - `symbol_named`, `imported`, `lookup_in_module`, `lookup_in_module_with_visited` - -One rendering still compared against a declaration's own, which goes when the -impl index carries `DefId`s: - -- `impl_target_decl_key` — a receiver's newtype chain against an impl's head +- `decl_in_module` — `lookup_in_module` read back as an identity, for the + positions no reference site answers: `builtin::f`, a namespace member, + `core:rt`'s `panic` at a synthesised call, and a default expression's own + module. Each names its module rather than searching for one, so no vantage + is supplied. + +Renderings still compared against a declaration's own name: + +- `impl_target_decl_key` — a receiver's newtype chain against an impl's head, on + the paths that name no block: an auto-derived `Eq` / `Ord`, and a method + reached through a type parameter's bound, whose block monomorphization picks. + A dispatch that matched a concrete block reads the module off it instead. +- `impl_head_decl_name` — an impl header's own head, filtering a static call's + candidate blocks. Each side resolves in the module that wrote it, so an alias + on either steers neither. The Component Model boundary, permanent for the reason §9 gives: diff --git a/wado-compiler/src/defs.rs b/wado-compiler/src/defs.rs index 079629cbfb..43d044830c 100644 --- a/wado-compiler/src/defs.rs +++ b/wado-compiler/src/defs.rs @@ -83,7 +83,12 @@ pub enum DefKind { VariantCase, /// A `flags` member. FlagsMember, - /// A method declared by a trait, a resource, or an effect interface. + /// An `impl` block. It writes no name, so nothing can import it and no + /// scope answers for it; its identity exists so the methods it declares + /// have an owner and its own facts have a key. + Impl, + /// A method declared by a trait, a resource, an effect interface, or an + /// `impl` block. Method, } @@ -206,20 +211,22 @@ impl DefTable { // it would otherwise be the one type in the language with no // declaration to name it. for item in &ast.items { - if let Item::TupleTypeDecl(decl) = item - && !table.by_ast_id.contains_key(&decl.id) - { - table.declare(Def { - ast_id: decl.id, - module: module.clone(), - name: crate::name::TUPLE_TYPE_NAME.to_string(), - kind: DefKind::BuiltinType, - visibility: decl.visibility, - span: Some(decl.span), - parent: None, - function_local: false, - members: Vec::new(), - }); + match item { + Item::TupleTypeDecl(decl) if !table.by_ast_id.contains_key(&decl.id) => { + table.declare(Def { + ast_id: decl.id, + module: module.clone(), + name: crate::name::TUPLE_TYPE_NAME.to_string(), + kind: DefKind::BuiltinType, + visibility: decl.visibility, + span: Some(decl.span), + parent: None, + function_local: false, + members: Vec::new(), + }); + } + Item::Impl(block) => table.declare_impl_block(module, block, false), + _ => {} } } table.declare_members(module, ast); @@ -256,12 +263,41 @@ impl DefTable { } } + /// Identify an `impl` block, which no symbol table row names. Its name is + /// empty because it writes none, and no spelling reaches what has none. + fn declare_impl_block( + &mut self, + module: &ModuleSource, + block: &crate::ast::ImplBlock, + function_local: bool, + ) { + if self.by_ast_id.contains_key(&block.id) { + return; + } + self.declare(Def { + ast_id: block.id, + module: module.clone(), + name: String::new(), + kind: DefKind::Impl, + visibility: Visibility::Private, + span: Some(block.span), + parent: None, + function_local, + members: Vec::new(), + }); + } + /// Identify a function-local item (`Stmt::Item`) and its members. /// /// A local `struct` is a declaration like any other — two functions writing /// the same spelling declare two of them — so it gets an identity rather /// than a mangled storage name standing in for one. fn declare_local_item(&mut self, module: &ModuleSource, item: &Item) { + if let Item::Impl(block) = item { + self.declare_impl_block(module, block, true); + self.declare_item_members(module, item); + return; + } let (kind, name) = match item { Item::Struct(d) => (DefKind::Struct, &d.name), Item::Enum(d) => (DefKind::Enum, &d.name), @@ -343,6 +379,15 @@ impl DefTable { i.methods.iter().map(|m| (m.id, &m.name, None, m.span)), ), ), + Item::Impl(b) => ( + b.id, + members( + DefKind::Method, + b.methods + .iter() + .map(|m| (m.id, &m.name, Some(m.visibility), m.span)), + ), + ), _ => return, }; let Some(owner) = self.of_ast_id(owner) else { @@ -439,6 +484,14 @@ impl DefTable { /// nothing. /// /// This is not a name lookup: the node is already the declaration. + /// [`Self::of_ast_id`] for a node the collect pass identified, so a miss is + /// a hole in that pass rather than a case to handle. + #[must_use] + pub fn def_at(&self, id: AstId) -> DefId { + self.of_ast_id(id) + .unwrap_or_else(|| panic!("{id:?} declares nothing")) + } + #[must_use] pub fn of_ast_id(&self, id: AstId) -> Option { self.by_ast_id.get(&id).copied() @@ -637,6 +690,68 @@ mod tests { assert_eq!(field_names(widgets[1]), ["b"]); } + /// An `impl` block is a declaration: two blocks writing the same method + /// name declare two methods, and each block owns its own. + #[test] + fn an_impl_block_and_its_methods_are_declarations() { + let source = r#" + pub struct Point { x: i32 } + pub struct Line { a: i32 } + impl Point { pub fn len(&self) -> i32 { return self.x; } } + impl Line { pub fn len(&self) -> i32 { return self.a; } } + "#; + let (defs, module) = build_from_source(source); + let blocks: Vec = defs + .iter() + .filter(|d| defs.kind(*d) == DefKind::Impl) + .collect(); + assert_eq!(blocks.len(), 2); + assert_ne!(blocks[0], blocks[1]); + // No spelling reaches an `impl` block, so it renders none. + assert_eq!(defs.name(blocks[0]), ""); + assert_eq!(defs.module(blocks[0]), &module); + + for block in &blocks { + assert_eq!(defs.members(*block).len(), 1); + let method = defs.members(*block)[0]; + assert_eq!(defs.name(method), "len"); + assert_eq!(defs.kind(method), DefKind::Method); + assert_eq!(defs.parent(method), Some(*block)); + } + assert_ne!(defs.members(blocks[0])[0], defs.members(blocks[1])[0]); + } + + /// A local `impl` block is declared by the function that writes it, so its + /// methods are identified like any other block's. + /// + /// Identity only: `TraitEnv::build` walks a module's own items, so nothing + /// a local block declares is dispatchable yet. Giving it one is what that + /// walk needs to reach `Stmt::Item`, not something this table withholds. + #[test] + fn a_function_local_impl_block_is_a_declaration_of_its_own() { + let source = r#" + pub fn run() -> i32 { + struct Widget { a: i32 } + impl Widget { fn get(&self) -> i32 { return self.a; } } + let w = Widget { a: 1 }; + return w.get(); + } + "#; + let (defs, _) = build_from_source(source); + let block = defs + .iter() + .find(|d| defs.kind(*d) == DefKind::Impl) + .expect("the local impl block is a declaration"); + assert!(defs.is_function_local(block)); + assert_eq!( + defs.members(block) + .iter() + .map(|m| defs.name(*m)) + .collect::>(), + ["get"] + ); + } + /// A cached declaration fact carries a [`DefId`], so a later compile that /// re-identifies the same declarations must hand back the same ones — the /// stdlib snapshot reads its `ImplSig`s back this way. @@ -645,6 +760,7 @@ mod tests { let source = r#" pub struct Point { x: i32, y: i32 } pub trait Greet { fn hello(&self) -> i32; } + impl Point { pub fn len(&self) -> i32 { return self.x; } } "#; let (seed, modules, symbols, _) = analyze_source(source); let again = DefTable::build_seeded(Some(&seed), &modules, &symbols); diff --git a/wado-compiler/src/elaborator.rs b/wado-compiler/src/elaborator.rs index 1a4c716a30..2eddf6763c 100644 --- a/wado-compiler/src/elaborator.rs +++ b/wado-compiler/src/elaborator.rs @@ -256,7 +256,6 @@ impl<'a, H: CompilerHost> Elaborator<'a, H> { /// The symbol `name` reaches from `module`, for a caller whose reference site /// is not at hand — a mangled name, a synthesis target. No scope is run. - /// Prefer [`Self::symbol_at`], which reads the answer the walk recorded. pub(crate) fn symbol_named( &self, module: &ModuleSource, @@ -276,16 +275,6 @@ impl<'a, H: CompilerHost> Elaborator<'a, H> { self.symbols.get(&self.tysys.resolutions.defs().ast_id(def)) } - /// The symbol row behind a reference site. - /// - /// The walk answered for the site, so the declaration — and with it the - /// row — comes from what the name means where it was *written*, with no - /// second scope run beside the first. - pub(crate) fn symbol_at(&self, site: crate::ast::AstId) -> Option<&'a crate::symbol::Symbol> { - let def = self.tysys.resolutions.declared_if_walked(site)?; - self.symbols.get(&self.tysys.resolutions.defs().ast_id(def)) - } - /// Construct a [`TypeLookup`] view over the elaborator's current import /// context and shared `all_*` tables. Use this for any type-name /// resolution; never reach into `all_*` directly. @@ -455,6 +444,64 @@ impl<'a, H: CompilerHost> Elaborator<'a, H> { self.insert_reference(use_id, def_id); } + /// The free function the reference site `site` names, answered by the + /// module that wrote it (WEP 2026-08-12). `None` where it names something + /// else — a binder, a variant case, a node no walk saw. + pub(super) fn free_function_at(&self, site: crate::ast::AstId) -> Option { + let def = self.tysys.resolutions.declared_if_walked(site)?; + (self.tysys.resolutions.defs().kind(def) == crate::defs::DefKind::Function).then_some(def) + } + + /// The canonical signature of the free function the site names. + pub(super) fn free_function_sig_at( + &self, + site: crate::ast::AstId, + ) -> Option<&sem::decls::FunctionSig> { + self.tysys + .signatures + .function_sig(self.free_function_at(site)?) + } + + /// The declaration `id` declares. See [`crate::defs::DefTable::def_at`]. + pub(super) fn def_at(&self, id: crate::ast::AstId) -> crate::defs::DefId { + self.tysys.resolutions.defs().def_at(id) + } + + /// The declaration `module` declares under `name`, for the positions no + /// reference site answers. The module is named by the caller, not searched + /// for. + pub(super) fn decl_in_module( + &self, + module: &ModuleSource, + name: &str, + ) -> Option { + self.symbols + .lookup_in_module(module, name) + .and_then(|sym| self.tysys.resolutions.defs().of_ast_id(sym.defined_at)) + } + + /// [`Self::decl_in_module`] as a callee identity. + fn callee_in_module(&self, module: &ModuleSource, name: &str) -> Option { + Some(self.callee_of(self.decl_in_module(module, name)?)) + } + + /// The callee identity of the declaration `def`. + fn callee_of(&self, def: crate::defs::DefId) -> callee::CalleeRef { + callee::CalleeRef::declared(self.tysys.resolutions.defs(), def) + } + + /// Record a use→def edge naming the declaration `def`. The map is keyed by + /// node on both sides, so the declaring node is read off the identity here + /// rather than carried beside it. + pub(super) fn record_reference_to_decl( + &mut self, + use_id: crate::ast::AstId, + def: crate::defs::DefId, + ) { + let node = self.tysys.resolutions.defs().ast_id(def); + self.insert_reference(use_id, node); + } + /// Record that an identifier resolved to a declared symbol reachable from /// the current module under `name` (local item, imported item, imported /// namespace member, etc.). Looks up the defining [`AstId`](crate::ast::AstId) through @@ -541,7 +588,7 @@ impl<'a, H: CompilerHost> Elaborator<'a, H> { &self, receiver: &trait_env::ImplTargetKey, method_name: &str, - ) -> Option { + ) -> Option { self.tysys .trait_env .static_method_index @@ -1759,13 +1806,16 @@ impl<'a, H: CompilerHost> Elaborator<'a, H> { .then(|| self.tysys.resolutions.defs().of_ast_id(decl_id)) .flatten(); let ops = self.resolve_effect_ops(&type_params, &methods, resource_self); + let defs = std::sync::Arc::clone(self.tysys.resolutions.defs()); + let owner = defs.def_at(decl_id); for method in &methods { + let op = defs.def_at(method.id); self.sem .decls .resource_method_ids - .insert((decl_id, method.name.clone()), method.id); + .insert((owner, method.name.clone()), op); } - self.sem.decls.effect_ops.insert(decl_id, ops); + self.sem.decls.effect_ops.insert(owner, ops); } // Pre-populate the generic-function inference caches for every @@ -1774,11 +1824,13 @@ impl<'a, H: CompilerHost> Elaborator<'a, H> { // later in the file) to infer type arguments at the call site // during body resolution, without relying on a later // monomorphization-time fallback. - let mut function_sigs: IndexMap = IndexMap::default(); + let mut function_sigs: IndexMap> = + IndexMap::default(); for item in &module.items { if let Item::Function(func) = item { + let def = self.def_at(func.id); let sig = self.record_function_sig(func); - function_sigs.insert(func.name.clone(), sig); + function_sigs.insert(def, Rc::new(sig)); } } for item in &module.items { @@ -2071,10 +2123,11 @@ impl<'a, H: CompilerHost> Elaborator<'a, H> { for method in &impl_block.methods { // Records-only: reify emits the method `TirFunction` // from the recorded signature facts + the AST. + let method_def = scope.def_at(method.id); let recorded_sig = scope .tysys .signatures - .method_sig(method.id) + .method_sig(method_def) .cloned() .expect("the decl pass records every impl-declared method's canonical signature"); scope.resolve_method( diff --git a/wado-compiler/src/elaborator/call.rs b/wado-compiler/src/elaborator/call.rs index c92ea5acc9..a832e28bd1 100644 --- a/wado-compiler/src/elaborator/call.rs +++ b/wado-compiler/src/elaborator/call.rs @@ -13,7 +13,6 @@ use super::callee::{CalleeRef, StaticMethodRef}; use super::infer::InferCtx; use super::instantiate::Instantiation; use super::scope::Scope; -use super::sem::decls::FunctionSig; use super::sig::MethodSig; use super::trait_env; use super::trait_env::ImplTargetKey; @@ -136,6 +135,16 @@ impl CalleeIdentKind<'_> { } } + /// The reference site of the callee itself, which says which declaration a + /// bare `name(…)` means. `Rewritten` is synthesised from an already-resolved + /// `Self::` / `T::` prefix, so no walk saw it. + fn callee_site(&self) -> Option { + match self { + Self::AsIs(ident) => Some(ident.id), + Self::Rewritten(_) | Self::AbstractTypeParam { .. } => None, + } + } + /// The reference site of a qualified callee's receiver segment — the `Type` of /// `Type::method`, which the walk answered for in the module that wrote it. /// @@ -318,6 +327,22 @@ impl Elaborator<'_, H> { self.is_effect_or_resource_decl(def).then_some(def) } + /// Whether the name spells a built-in variant case (`Ok`, `Err`, `Some`, + /// `None`). Read from the `CompilerItem` registry, so a stdlib rename + /// carries here without re-editing a literal set. + fn names_result_or_option_case(&self, name: &str) -> bool { + let tt = self.tysys.type_table.borrow(); + let items = tt.compiler_items(); + [ + crate::compiler_item::CompilerItem::ResultOk, + crate::compiler_item::CompilerItem::ResultErr, + crate::compiler_item::CompilerItem::OptionSome, + crate::compiler_item::CompilerItem::OptionNone, + ] + .into_iter() + .any(|item| name == items.variant_case_name(item)) + } + pub(super) fn resolve_call( &mut self, call: &ast::CallExpr, @@ -494,8 +519,11 @@ impl Elaborator<'_, H> { } // First, determine expected parameter types to handle coercion. - let (mut param_types, callee_slots) = - self.lookup_function_signature(effective_name, receiver_site); + let (mut param_types, callee_slots) = self.lookup_function_signature( + effective_name, + receiver_site, + callee_kind.callee_site(), + ); // Instantiate the callee's slots before an argument is resolved // against one of its parameter types. A rigid slot is the callee's @@ -533,6 +561,7 @@ impl Elaborator<'_, H> { call.span, ctx, &mut param_types, + None, ) { return TypeTable::ERROR; } @@ -658,7 +687,7 @@ impl Elaborator<'_, H> { // Builtin functions: resolve through core:builtin module if prefix == "builtin" { ( - Some(CalleeRef::new(ModuleSource::builtin(), suffix)), + self.callee_in_module(&ModuleSource::builtin(), suffix), effective_name.to_string(), ) } @@ -686,8 +715,8 @@ impl Elaborator<'_, H> { } else { None }; - let method_ast_id = self - .locate_static_method_impl(prefix, suffix, arg_hint.as_deref()) + let method_def = self + .locate_static_method_impl(prefix, suffix, arg_hint.as_deref(), None) .and_then(|r| r.method_id) .or_else(|| { self.static_method_decl_id( @@ -695,8 +724,8 @@ impl Elaborator<'_, H> { suffix, ) }); - if let Some(method_ast_id) = method_ast_id { - self.record_reference_to_def(suffix_seg.id, method_ast_id); + if let Some(method_def) = method_def { + self.record_reference_to_decl(suffix_seg.id, method_def); } } // Resolve method-level type args (e.g., i32::deserialize::) @@ -1198,6 +1227,7 @@ impl Elaborator<'_, H> { type_name, method_name, arg_type_hint.as_deref(), + None, ); let method_ref = resolved.unwrap_or_else(|| { StaticMethodRef::new(ns_source.clone(), type_name, method_name, None, None) @@ -1211,7 +1241,7 @@ impl Elaborator<'_, H> { // `ns::Type::method`, so the method is its third segment — // the position `record_namespaced_case` also reads. if let Some(method_seg) = ident.segments.get(2) - && let Some(method_ast_id) = method_ref.method_id.or_else(|| { + && let Some(method_def) = method_ref.method_id.or_else(|| { // The receiver is `ns::Type`, whose middle segment // the resolve walk answered for under the `ns$Type` // alias. No spelling is re-resolved from the call @@ -1224,7 +1254,7 @@ impl Elaborator<'_, H> { self.static_method_decl_id(&receiver, method_name) }) { - self.record_reference_to_def(method_seg.id, method_ast_id); + self.record_reference_to_decl(method_seg.id, method_def); } // Qualify by the module the impl was located in: @@ -1266,7 +1296,7 @@ impl Elaborator<'_, H> { let ns_key = self.namespace_member(prefix, type_name).map(|def| { trait_env::ImplTargetKey::of_decl(self.tysys.resolutions.defs(), def) }); - let param_is_mut = self.lookup_static_method_param_is_mut_keyed( + let param_is_mut = self.lookup_static_method_param_is_mut( type_name, method_name, ns_key.as_ref(), @@ -1289,7 +1319,7 @@ impl Elaborator<'_, H> { // Recorded so reify replays this Call shape without re-running // dispatch. Empty defaults would leave codegen a call short an // argument; empty types would leave reify padding untyped. - let param_defaults = self.lookup_static_method_param_defaults_keyed( + let param_defaults = self.lookup_static_method_param_defaults( type_name, method_name, ns_key.as_ref(), @@ -1355,7 +1385,7 @@ impl Elaborator<'_, H> { } } ( - Some(CalleeRef::new(ns_source, suffix)), + self.callee_in_module(&ns_source, suffix), effective_name.to_string(), ) } @@ -1385,87 +1415,37 @@ impl Elaborator<'_, H> { (None, effective_name.to_string()) } } - // Check if it's a local function (defined in this module) or - // a built-in type constructor (Ok, Err, Some, None). - // The four constructor names flow through the - // `CompilerItem` registry so a stdlib rename of any of - // them is picked up here without re-editing the literal set. - else if self - .sem - .decls - .function_return_types - .contains_key(effective_name) - || { - let tt = self.tysys.type_table.borrow(); - let items = tt.compiler_items(); - effective_name - == items.variant_case_name(crate::compiler_item::CompilerItem::ResultOk) - || effective_name - == items.variant_case_name(crate::compiler_item::CompilerItem::ResultErr) - || effective_name - == items.variant_case_name(crate::compiler_item::CompilerItem::OptionSome) - || effective_name - == items.variant_case_name(crate::compiler_item::CompilerItem::OptionNone) - } + // The call's own reference site, answered by the module that wrote it + // (WEP 2026-08-12) — not by the module the walk is standing in, which + // for a parameter default is the caller's. + else if let Some(callee) = + self.tysys + .resolutions + .declared_if_walked(ident.id) + .filter(|def| { + self.tysys.resolutions.defs().kind(*def) == crate::defs::DefKind::Function + }) { - self.record_item_reference_by_name(ident.id, effective_name); - ( - Some(CalleeRef::local( - &self.current_module_source, - effective_name.to_string(), - )), - effective_name.to_string(), - ) + self.record_reference_to_decl(ident.id, callee); + (Some(self.callee_of(callee)), effective_name.to_string()) } - // Check for prelude functions (panic, unreachable) - // These are defined in core:rt and re-exported by core:prelude + // `panic` / `unreachable` where no site answered — a synthesised call. + // A module declaring either name of its own is answered above. else if matches!(effective_name, "panic" | "unreachable") { ( - Some(CalleeRef::rt_prelude(effective_name)), + self.callee_in_module(&ModuleSource::rt(), effective_name), effective_name.to_string(), ) } - // Check if this is an imported function (per-module imports). - // We go through the `Symbol` directly so both the use→def edge - // and the `CalleeRef` come from the same resolution — this is - // the single place the alias→defining-name translation happens. - else if self.sem.decls.imported_functions.contains(effective_name) { - if let Some(symbol) = self.symbol_named(&self.current_module_source, effective_name) { - self.record_reference_to_def(ident.id, symbol.defined_at); - ( - Some(CalleeRef::from_imported_symbol(symbol)), - effective_name.to_string(), - ) - } else { - // Imported but not in symbols - shouldn't happen but allow - ( - Some(CalleeRef::local( - &self.current_module_source, - effective_name.to_string(), - )), - effective_name.to_string(), - ) - } - } - // Fallback: when resolving a default expression, a free-function - // call may target a private function of the callee's declaring - // module (see `default_scope_module`). `resolve_ident` already - // consults this fallback for bare identifiers / function refs; - // mirror it here for the *call* case so `helper()` in a default - // resolves in the defining module instead of erroring at the use - // site. The `CalleeRef`'s module drives `lookup_function_return_type` - // / `lookup_function_signature`, so the signature resolves in the - // defining module too. - else if let Some(fallback) = self.annotate_ctx.default_scope_module.clone() - && fallback != self.current_module_source - && self - .tysys - .signatures - .function_sig(&fallback, effective_name) - .is_some() - { + // A built-in type constructor — a variant case, not a function, so the + // site above declines it. + else if self.names_result_or_option_case(effective_name) { + self.record_item_reference_by_name(ident.id, effective_name); ( - Some(CalleeRef::new(fallback, effective_name.to_string())), + Some(CalleeRef::rendered( + self.current_module_source.clone(), + effective_name, + )), effective_name.to_string(), ) } else { @@ -1483,7 +1463,7 @@ impl Elaborator<'_, H> { name: display_name.clone(), span: call.span, }); - CalleeRef::local(&self.current_module_source, display_name) + CalleeRef::rendered(self.current_module_source.clone(), display_name) }; // Resolve explicit type arguments (`_` resolves to UNKNOWN). @@ -1615,8 +1595,8 @@ impl Elaborator<'_, H> { let param_is_mut = self.lookup_function_param_is_mut(&call.callee); let func_ref = FunctionRef { - module_source: callee.module, - name: callee.name, + module_source: callee.module().clone(), + name: callee.name().to_string(), monomorph_info: None, method_info: None, // Free function call, }; @@ -1710,8 +1690,8 @@ impl Elaborator<'_, H> { callee: &CalleeRef, receiver_site: Option, ) -> TypeId { - let callee_module = &callee.module; - let func_name = callee.name.as_str(); + let callee_module = callee.module(); + let func_name = callee.name(); // Handle builtin functions if callee_module.is_core_builtin() { return self.get_builtin_return_type(func_name); @@ -1737,8 +1717,8 @@ impl Elaborator<'_, H> { return return_type; } - if !callee_module.is_entry_point() - && let Some(sig) = self.tysys.signatures.function_sig(callee_module, func_name) + if let Some(def) = callee.def() + && let Some(sig) = self.tysys.signatures.function_sig(def) && let Some(return_type) = sig.decl.return_type { return return_type; @@ -1759,11 +1739,10 @@ impl Elaborator<'_, H> { effect: crate::defs::DefId, operation: &str, ) -> Option<(Vec, Option)> { - let decl_id = self.tysys.resolutions.defs().ast_id(effect); let sig = self .tysys .signatures - .resource_method_sig(decl_id, operation)?; + .resource_method_sig(effect, operation)?; Some((sig.decl.param_types.clone(), sig.decl.return_type)) } @@ -1800,6 +1779,7 @@ impl Elaborator<'_, H> { &mut self, name: &str, receiver_site: Option, + callee_site: Option, ) -> (Vec, Vec) { // Check for qualified name (Type::method or Effect::operation) if let Some(pos) = name.find("::") { @@ -1820,10 +1800,8 @@ impl Elaborator<'_, H> { // Builtin functions: look up param types from core:builtin module if prefix == "builtin" - && let Some(sig) = self - .tysys - .signatures - .function_sig(&ModuleSource::builtin(), suffix) + && let Some(def) = self.decl_in_module(&ModuleSource::builtin(), suffix) + && let Some(sig) = self.tysys.signatures.function_sig(def) { return (sig.decl.param_types.clone(), Vec::new()); } @@ -1840,7 +1818,9 @@ impl Elaborator<'_, H> { // parameter and reaches codegen mismatched. if self.sem.imports.namespace_imports.contains_key(prefix) { let ns_source = self.sem.imports.namespace_imports[prefix].clone(); - if let Some(sig) = self.tysys.signatures.function_sig(&ns_source, suffix) { + if let Some(def) = self.decl_in_module(&ns_source, suffix) + && let Some(sig) = self.tysys.signatures.function_sig(def) + { return ( sig.decl.param_types.clone(), sig.decl.type_params.iter().map(|(_, id)| *id).collect(), @@ -1859,7 +1839,7 @@ impl Elaborator<'_, H> { Some(&ns_key), ); if !params.is_empty() { - let slots = self.lookup_static_method_slots_keyed(method_name, &ns_key); + let slots = self.lookup_static_method_slots(method_name, &ns_key); return (params, slots); } } @@ -1867,37 +1847,15 @@ impl Elaborator<'_, H> { return (Vec::new(), Vec::new()); } - let slots = |sig: &FunctionSig| sig.decl.type_params.iter().map(|(_, id)| *id).collect(); - - if let Some(sig) = self - .tysys - .signatures - .function_sig(&self.current_module_source, name) - { - return (sig.decl.param_types.clone(), slots(sig)); - } - - // Imported functions: the canonical signature resolved in the - // definition module's perspective, so same-named types from - // different modules can't be confused. - if let Some(symbol) = self.symbol_named(&self.current_module_source, name) { - let src = symbol.module_source().clone(); - let sym_name = symbol.name.clone(); - if let Some(sig) = self.tysys.signatures.function_sig(&src, &sym_name) { - return (sig.decl.param_types.clone(), slots(sig)); - } - } - - // A default expression may call a private free function of its - // declaring module (`default_scope_module`). - if let Some(fallback) = self.annotate_ctx.default_scope_module.clone() - && fallback != self.current_module_source - && let Some(sig) = self.tysys.signatures.function_sig(&fallback, name) - { - return (sig.decl.param_types.clone(), slots(sig)); - } - - (Vec::new(), Vec::new()) + // One read for this module's functions, its imports under either + // spelling, and a default expression's callee scope. + let Some(sig) = callee_site.and_then(|site| self.free_function_sig_at(site)) else { + return (Vec::new(), Vec::new()); + }; + ( + sig.decl.param_types.clone(), + sig.decl.type_params.iter().map(|(_, id)| *id).collect(), + ) } /// Fill missing trailing arguments from the callee's declared defaults, @@ -1981,30 +1939,18 @@ impl Elaborator<'_, H> { if let Some(defaults) = ctx.closure_defaults.get(&ident.name) { return (defaults.clone(), None); } - // A qualified name is never a function of the writing module, so only - // the site below can answer for one — `ns::f` included. - if !ident.name.contains("::") - && let Some(sig) = self - .tysys - .signatures - .function_sig(&self.current_module_source, &ident.name) - { - return ( - crate::elaborator::sig::Param::named_defaults(&sig.params), - Some(self.current_module_source.clone()), - ); - } - if let Some(symbol) = self.symbol_at(ident.id) { - let src = symbol.module_source().clone(); - let name = symbol.name.clone(); - if let Some(sig) = self.tysys.signatures.function_sig(&src, &name) { - return ( - crate::elaborator::sig::Param::named_defaults(&sig.params), - Some(src), - ); - } - } - (Vec::new(), None) + let Some(def) = self.free_function_at(ident.id) else { + return (Vec::new(), None); + }; + let Some(sig) = self.tysys.signatures.function_sig(def) else { + return (Vec::new(), None); + }; + // A default resolves in the declaring module's scope, which is the + // callee's own — never the caller's, even under the same spelling. + ( + crate::elaborator::sig::Param::named_defaults(&sig.params), + Some(self.tysys.resolutions.defs().module(def).clone()), + ) } /// Look up whether each parameter of a free function is `mut`. @@ -2019,24 +1965,9 @@ impl Elaborator<'_, H> { return Vec::new(); } - if let Some(sig) = self - .tysys - .signatures - .function_sig(&self.current_module_source, &ident.name) - { - return crate::elaborator::sig::Param::is_mut_flags(&sig.params); - } - - // Imported function - if let Some(symbol) = self.symbol_at(ident.id) { - let src = symbol.module_source().clone(); - let name = symbol.name.clone(); - if let Some(sig) = self.tysys.signatures.function_sig(&src, &name) { - return crate::elaborator::sig::Param::is_mut_flags(&sig.params); - } - } - - Vec::new() + self.free_function_sig_at(ident.id) + .map(|sig| crate::elaborator::sig::Param::is_mut_flags(&sig.params)) + .unwrap_or_default() } /// Infer a generic call's type arguments from its actual argument types, in @@ -2052,7 +1983,7 @@ impl Elaborator<'_, H> { expected_type: Option, span: crate::token::Span, ) -> Vec { - let func_name = callee.name.as_str(); + let func_name = callee.name(); // Builtin functions: pull type-param / param / return info from the // BuiltinRegistry so that calls like `builtin::select(a, b, c)` and // `builtin::array_new(n)` infer their generic type parameters from @@ -2286,7 +2217,7 @@ impl Elaborator<'_, H> { .map(|n| format!("`{n}`")) .collect::>() .join(", "); - let func_name = callee.name.as_str(); + let func_name = callee.name(); let _ = self.emit(TypeError::CannotInferType { message: format!( "cannot infer type parameter {names} of function `{func_name}`; \ @@ -2381,7 +2312,7 @@ impl Elaborator<'_, H> { let n = space.len(); let defaults: Vec> = - self.with_default_scope_module(Some(callee.module.clone()), |s| { + self.with_default_scope_module(Some(callee.module().clone()), |s| { let mut scope = s.enter_inherited_type_param_scope(); scope.annotate_ctx.trait_ctx.type_params.clear(); scope.register_generic_params(¶ms, 0); @@ -2500,7 +2431,7 @@ impl Elaborator<'_, H> { return; } - let func_name = callee.name.as_str(); + let func_name = callee.name(); let message = format!( "cannot infer type parameter {} of function `{func_name}`; \ add a turbofish (`{func_name}::<...>()`) or a type annotation", @@ -2646,10 +2577,7 @@ impl Elaborator<'_, H> { &self, callee: &CalleeRef, ) -> Option<(Vec<(String, TypeId)>, Vec, Option)> { - let sig = self - .tysys - .signatures - .function_sig(&callee.module, &callee.name)?; + let sig = self.tysys.signatures.function_sig(callee.def()?)?; if sig.type_param_ids.is_empty() { return Some((vec![], vec![], None)); } @@ -2688,7 +2616,7 @@ impl Elaborator<'_, H> { expected_type: Option, span: crate::token::Span, ) -> (Vec, Vec) { - let probe = CalleeRef::local(&self.current_module_source, suffix.to_string()); + let probe = CalleeRef::rendered(self.current_module_source.clone(), suffix); let method_args = self.infer_fn_type_args(&probe, raw_args, args, expected_type, span); if !method_args.is_empty() { return (Vec::new(), method_args); @@ -2835,30 +2763,10 @@ impl Elaborator<'_, H> { return Vec::new(); }; - let param_types: Vec = if self - .sem - .decls - .function_return_types - .contains_key(&ident.name) - { - match self - .tysys - .signatures - .function_sig(&self.current_module_source, &ident.name) - { - Some(sig) => sig.decl.param_types.clone(), - None => return Vec::new(), - } - } else if let Some(symbol) = self.symbol_at(ident.id) { - let src = symbol.module_source().clone(); - let name = symbol.name.clone(); - match self.tysys.signatures.function_sig(&src, &name) { - Some(sig) => sig.decl.param_types.clone(), - None => return Vec::new(), - } - } else { + let Some(sig) = self.free_function_sig_at(ident.id) else { return Vec::new(); }; + let param_types: Vec = sig.decl.param_types.clone(); // Substitute type params with explicit type args param_types diff --git a/wado-compiler/src/elaborator/callee.rs b/wado-compiler/src/elaborator/callee.rs index b69f6e221e..5b2d119f6c 100644 --- a/wado-compiler/src/elaborator/callee.rs +++ b/wado-compiler/src/elaborator/callee.rs @@ -1,56 +1,69 @@ -//! Resolved call-target identities: `CalleeRef` and `StaticMethodRef` bundle the -//! `(module, name)` pair naming a free function or static method. Threaded -//! separately, the defining-module name and the caller-visible alias could drift -//! apart; the bundle leaves no way to split them and confines the -//! alias→defining-name translation to the factory methods below. +//! Resolved call-target identities: a free function callee is the declaration +//! it names (WEP 2026-08-12), a static method callee what dispatch picked. use crate::module_source::{ModuleSource, ModuleSourceInterner}; -use crate::symbol::Symbol; -/// Identity of a free function callee: the module where it is defined and the -/// name under which it is defined in that module. +/// Identity of a free function callee. `Declared` carries the module and name +/// its one constructor reads off the table, so TIR emission needs none at hand; +/// only `def` says which declaration this is, and nothing reads a name back. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(super) struct CalleeRef { - pub module: ModuleSource, - pub name: String, +pub(super) enum CalleeRef { + Declared { + def: crate::defs::DefId, + module: ModuleSource, + name: String, + }, + /// A callee no declaration names in this currency: an effect operation, + /// whose module is the namespace that signature resolution, the effect + /// check, dispatch and WIR all key on rather than one any module declares. + Rendered { module: ModuleSource, name: String }, } impl CalleeRef { - pub fn new(module: ModuleSource, name: impl Into) -> Self { - Self { + /// The free function `def` declares, rendered once from the table. + pub fn declared(defs: &crate::defs::DefTable, def: crate::defs::DefId) -> Self { + Self::Declared { + def, + module: defs.module(def).clone(), + name: defs.name(def).to_string(), + } + } + + pub fn rendered(module: ModuleSource, name: impl Into) -> Self { + Self::Rendered { module, name: name.into(), } } - /// A callee defined in the current module (local function). - pub fn local(current_module: &ModuleSource, name: impl Into) -> Self { - Self::new(current_module.clone(), name) + /// A callee reached through `Prefix::name` where `Prefix` names an effect + /// or resource rather than a module, so the prefix itself is the namespace. + pub fn local_namespace( + interner: &mut ModuleSourceInterner, + prefix: &str, + name: impl Into, + ) -> Self { + Self::rendered(interner.local(prefix), name) } - /// A callee imported into the current module via `use`. Translates the - /// caller-visible alias (under which the symbol is keyed in the current - /// module's symbol table) to the defining-module name in a single place. - pub fn from_imported_symbol(symbol: &Symbol) -> Self { - Self::new(symbol.module_source().clone(), symbol.name.clone()) + /// The declaration this names, or `None` for a [`Self::Rendered`] callee. + pub fn def(&self) -> Option { + match self { + Self::Declared { def, .. } => Some(*def), + Self::Rendered { .. } => None, + } } - /// A callee in `core:rt` (prelude functions like `panic`, `unreachable`). - pub fn rt_prelude(name: impl Into) -> Self { - Self::new(ModuleSource::rt(), name) + pub fn module(&self) -> &ModuleSource { + match self { + Self::Declared { module, .. } | Self::Rendered { module, .. } => module, + } } - /// A callee reached through a namespace-qualified call `Prefix::name` - /// where `Prefix` is a module path (e.g. `Stdout::write`). The - /// `prefix` is interned through the elaborator's - /// [`crate::module_source::ModuleSourceInterner`] and wrapped in a - /// `ModuleSource::Local`. - pub fn local_namespace( - interner: &mut ModuleSourceInterner, - prefix: &str, - name: impl Into, - ) -> Self { - Self::new(interner.local(prefix), name) + pub fn name(&self) -> &str { + match self { + Self::Declared { name, .. } | Self::Rendered { name, .. } => name, + } } } @@ -63,9 +76,9 @@ pub(super) struct StaticMethodRef { pub type_name: String, pub method_name: String, pub trait_name: Option, - /// The node declaring the method this selection picked. `None` when no - /// declaration backs it — the auto-derived `Default::default`. - pub method_id: Option, + /// The method this selection picked. `None` when no declaration backs + /// it — the auto-derived `Default::default`. + pub method_id: Option, } impl StaticMethodRef { @@ -74,7 +87,7 @@ impl StaticMethodRef { type_name: impl Into, method_name: impl Into, trait_name: Option, - method_id: Option, + method_id: Option, ) -> Self { Self { module, diff --git a/wado-compiler/src/elaborator/expr.rs b/wado-compiler/src/elaborator/expr.rs index dcb3415b5e..d39e57fe98 100644 --- a/wado-compiler/src/elaborator/expr.rs +++ b/wado-compiler/src/elaborator/expr.rs @@ -706,7 +706,7 @@ impl Elaborator<'_, H> { // and functions (issue #1486). if let Some(fallback) = self.annotate_ctx.default_scope_module.clone() && fallback != self.current_module_source - && let Some(result) = self.resolve_ident_in_fallback_module(&ident.name, &fallback) + && let Some(result) = self.resolve_ident_in_fallback_module(ident, &fallback) { return result; } @@ -723,17 +723,17 @@ impl Elaborator<'_, H> { /// default-expression resolution. Supports globals and function refs. fn resolve_ident_in_fallback_module( &mut self, - name: &str, + ident: &ast::IdentExpr, fallback: &ModuleSource, ) -> Option { // Reify resolves the fallback-module global / `FuncRef` its own // way; project the type only. This default-expr path is never an // assignment target, so no place is recorded. - let (owner, name) = self.declaring_module_of_ident(name, fallback); + let (owner, name) = self.declaring_module_of_ident(&ident.name, fallback); if let Some((ty, _)) = self.tysys.signatures.global(&owner, &name) { return Some(ty); } - let sig = self.tysys.signatures.function_sig(&owner, &name)?.clone(); + let sig = self.free_function_sig_at(ident.id)?.clone(); Some( self.compute_func_ref_type_from_sig(&sig, &[]) .unwrap_or(TypeTable::UNKNOWN), @@ -749,7 +749,9 @@ impl Elaborator<'_, H> { fallback: &ModuleSource, ) -> (ModuleSource, String) { if self.tysys.signatures.global(fallback, name).is_some() - || self.tysys.signatures.function_sig(fallback, name).is_some() + || self + .decl_in_module(fallback, name) + .is_some_and(|def| self.tysys.signatures.function_sig(def).is_some()) { return (fallback.clone(), name.to_string()); } @@ -969,8 +971,7 @@ impl Elaborator<'_, H> { ) -> TypeId { self.record_item_reference_by_name(ident.id, &ident.name); - let Some((sig, _def_module, _defining_name)) = self.lookup_func_sig_for_ref(&ident.name) - else { + let Some((sig, _def_module, _defining_name)) = self.lookup_func_sig_for_ref(ident) else { // Fallback: known function but its signature is unreachable // (shouldn't normally happen). Emit a stub FuncRef so downstream // stays sane. @@ -1087,24 +1088,12 @@ impl Elaborator<'_, H> { /// key space. fn lookup_func_sig_for_ref( &self, - name: &str, + ident: &ast::IdentExpr, ) -> Option<(super::sem::decls::FunctionSig, ModuleSource, String)> { - if let Some(sig) = self - .tysys - .signatures - .function_sig(&self.current_module_source, name) - { - return Some(( - sig.clone(), - self.current_module_source.clone(), - name.to_string(), - )); - } - let symbol = self.symbol_named(&self.current_module_source, name)?; - let src = symbol.module_source().clone(); - let original = symbol.name.clone(); - let sig = self.tysys.signatures.function_sig(&src, &original)?.clone(); - Some((sig, src, original)) + let def = self.free_function_at(ident.id)?; + let sig = self.tysys.signatures.function_sig(def)?.clone(); + let defs = self.tysys.resolutions.defs(); + Some((sig, defs.module(def).clone(), defs.name(def).to_string())) } /// Derive type arguments for a generic function reference from an expected @@ -5020,7 +5009,7 @@ impl Elaborator<'_, H> { // argument are header facts, so the impls are reached by the target's // canonical key rather than by scanning every module for one whose // written target name matches. - let declares_from = |key: &(ModuleSource, crate::ast::AstId)| -> bool { + let declares_from = |key: &crate::defs::DefId| -> bool { self.tysys .trait_env .impl_headers @@ -5042,10 +5031,11 @@ impl Elaborator<'_, H> { .trait_env .all_impl_keys(&self.impl_target(target_name)); // The current module wins a tie. + let defs = self.tysys.resolutions.defs(); keys.iter() - .find(|key| key.0 == self.current_module_source && declares_from(key)) + .find(|key| *defs.module(**key) == self.current_module_source && declares_from(key)) .or_else(|| keys.iter().find(|key| declares_from(key))) - .map(|(module, _)| module.clone()) + .map(|key| defs.module(*key).clone()) // The `From` impl may be synthesized later, so a miss is not an error. .unwrap_or_else(|| self.current_module_source.clone()) } diff --git a/wado-compiler/src/elaborator/item.rs b/wado-compiler/src/elaborator/item.rs index f054f8cac3..61a7f29136 100644 --- a/wado-compiler/src/elaborator/item.rs +++ b/wado-compiler/src/elaborator/item.rs @@ -888,8 +888,9 @@ impl TypeParamScope<'_, '_, H> { .self_type .expect("entering an impl frame binds Self to the target"); + let impl_def = scope.def_at(impl_block.id); scope.sem.decls.impl_sigs.insert( - impl_block.id, + impl_def, super::sig::ImplSig { self_type, target_type_args, @@ -1184,6 +1185,7 @@ impl Elaborator<'_, H> { /// instantiates a recorded signature instead of re-resolving the method /// AST under the *caller's* perspective (WEP 2026-05-26). pub(super) fn record_impl_decls(&mut self, impl_block: &ast::ImplBlock) { + let impl_def = self.def_at(impl_block.id); let mut block = self.enter_inherited_type_param_scope(); block.annotate_ctx.trait_ctx.type_params.clear(); block.annotate_ctx.trait_ctx.type_param_bounds.clear(); @@ -1258,10 +1260,11 @@ impl Elaborator<'_, H> { .first() .map(|p| p.self_kind) .unwrap_or(ast::SelfKind::None); + let method_def = frame_scope.def_at(method.id); frame_scope.sem.decls.method_sigs.insert( - method.id, + method_def, MethodSig { - ast_id: method.id, + def: method_def, decl: DeclSig { type_params, param_types, @@ -1279,7 +1282,7 @@ impl Elaborator<'_, H> { }) .collect(), declaring_slot_count, - declaring_impl: Some(impl_block.id), + declaring_impl: Some(impl_def), own_params: super::sig::own_params_of(&method.type_params), cm_name: method .attrs @@ -1505,12 +1508,14 @@ impl Elaborator<'_, H> { } } - /// Operation signatures the decl pass recorded for `decl_id`. + /// Operation signatures the decl pass recorded for the declaration at + /// `decl_id`. fn declared_effect_ops(&self, decl_id: ast::AstId) -> Vec { + let decl = self.def_at(decl_id); self.sem .decls .effect_ops - .get(&decl_id) + .get(&decl) .cloned() .expect("the decl pass records every interface / resource declaration's operations") } @@ -1613,11 +1618,12 @@ impl Elaborator<'_, H> { let mut type_params = decl_slots.clone(); type_params.extend(method_slots); + let method_def = method_scope.def_at(method.id); methods.insert( method.name.clone(), super::sig::TraitMethod { sig: MethodSig { - ast_id: method.id, + def: method_def, decl: DeclSig { type_params, param_types, @@ -1656,11 +1662,12 @@ impl Elaborator<'_, H> { } let module = scope.current_module_source.clone(); + let trait_def = scope.def_at(trait_decl.id); scope .sem .decls .trait_sigs - .insert(trait_decl.id, super::sig::TraitSig { module, methods }); + .insert(trait_def, super::sig::TraitSig { module, methods }); } /// Lower an effect or resource declaration's method list to [`TirEffectOp`]s, @@ -1830,10 +1837,11 @@ impl Elaborator<'_, H> { } else { SelfKind::None }; + let method_def = scope.def_at(method.id); scope.sem.decls.method_sigs.insert( - method.id, + method_def, MethodSig { - ast_id: method.id, + def: method_def, decl: DeclSig { type_params: decl_slots.clone(), param_types: params.iter().map(|p| p.type_id).collect(), @@ -2194,11 +2202,12 @@ impl Elaborator<'_, H> { /// re-resolution. Returns the declared return type for callers that /// need it (`resolve_function`'s `task_return_type`). fn populate_generic_function_cache(&mut self, func: &Function) -> TypeId { + let def = self.def_at(func.id); let sig = self .sem .decls .function_sigs - .get(&func.name) + .get(&def) .expect("decl pass records every free function's canonical signature"); let type_param_list = sig.decl.type_params.clone(); let resolved_param_types = sig.decl.param_types.clone(); @@ -2625,16 +2634,14 @@ impl Elaborator<'_, H> { (None, Some(d)) => (Some(d), true), (None, None) => (None, false), }; - let async_op = decl_ref - .map(|key| scope.tysys.resolutions.defs().ast_id(key)) - .and_then(|decl_id| { - scope - .tysys - .signatures - .resource_method_sig(decl_id, &func.name) - .filter(|op| op.is_async) - .map(|op| op.cm_name.is_some()) - }); + let async_op = decl_ref.and_then(|decl| { + scope + .tysys + .signatures + .resource_method_sig(decl, &func.name) + .filter(|op| op.is_async) + .map(|op| op.cm_name.is_some()) + }); if let Some(cm_backed) = async_op && (is_resource_effect || !cm_backed) { diff --git a/wado-compiler/src/elaborator/method_call.rs b/wado-compiler/src/elaborator/method_call.rs index 2be561eb5c..1746c5a4d2 100644 --- a/wado-compiler/src/elaborator/method_call.rs +++ b/wado-compiler/src/elaborator/method_call.rs @@ -497,7 +497,7 @@ impl Elaborator<'_, H> { // reify would try to lower a call to a function that does not exist. let method_found = method_info.is_some(); let MethodInfo { - method_ast_id: dispatched_method_ast_id, + method_def: dispatched_method_def, mut return_type, self_kind, param_types, @@ -525,7 +525,7 @@ impl Elaborator<'_, H> { }); // Default to Unknown type for error recovery MethodInfo { - method_ast_id: None, + method_def: None, return_type: TypeTable::UNKNOWN, self_kind: ast::SelfKind::Ref, param_types: vec![], @@ -1124,8 +1124,8 @@ impl Elaborator<'_, H> { // The target is the declaration dispatch selected, carried on its // signature. A name scan cannot stand in: two impls on one type can // declare the same method, and only dispatch knows which answered. - if let (Some(method_id), Some(def_id)) = (method_id, dispatched_method_ast_id) { - self.record_reference_to_def(method_id, def_id); + if let (Some(method_id), Some(def)) = (method_id, dispatched_method_def) { + self.record_reference_to_decl(method_id, def); } let func = FunctionRef { @@ -1465,11 +1465,9 @@ impl Elaborator<'_, H> { // `Type::::method()` parses as a static-method call and never // reaches `resolve_call`, which checks the bare spelling. The receiver // key comes from the resolved target type, as every lookup below does. - let static_receiver = struct_name_for_lookup.as_ref().map(|name| { - struct_key_for_lookup - .clone() - .unwrap_or_else(|| self.impl_target(name)) - }); + let static_receiver = struct_name_for_lookup + .as_ref() + .map(|name| self.static_receiver_key(name, struct_key_for_lookup.as_ref())); if let (Some(name), Some(receiver)) = (&struct_name_for_lookup, &static_receiver) { self.check_static_call_visibility( receiver, @@ -1507,6 +1505,7 @@ impl Elaborator<'_, H> { static_call.span, ctx, &mut param_types, + struct_key_for_lookup.as_ref(), ) { return TypeTable::ERROR; @@ -1516,7 +1515,7 @@ impl Elaborator<'_, H> { let static_method_defaults: Vec<(String, Option)> = struct_name_for_lookup .as_ref() .map(|name| { - self.lookup_static_method_param_defaults_keyed( + self.lookup_static_method_param_defaults( name, &static_call.method, struct_key_for_lookup.as_ref(), @@ -2120,11 +2119,16 @@ impl Elaborator<'_, H> { // its `method_id` is what the use→def edge below is recorded against. // A name lookup cannot stand in — two conversion impls on one type // declare the same `from`, and only the argument's type separates - // them. + // them. The receiver comes off the resolved type: re-deriving it from + // `struct_name` searches the caller's frame, which an aliased import + // leaves without that name at all. + let receiver_key = + self.impl_target_of(target_type_id, &crate::name::DeclName::new(&struct_name)); let selected = self.locate_static_method_impl( &struct_name, &static_call.method, arg_type_hint.as_deref(), + Some(&receiver_key), ); let trait_name_opt = selected.as_ref().and_then(|r| r.trait_name.clone()); @@ -2136,12 +2140,17 @@ impl Elaborator<'_, H> { // disagreement is reported here instead of ICE-ing there. if trait_name_opt.is_none() && let Some(arg_type) = arg_type_hint.as_deref() - && !self.has_inherent_static_method(&struct_name, &static_call.method) + && !self.has_inherent_static_method( + &struct_name, + &static_call.method, + Some(&receiver_key), + ) && self.report_unmatched_conversion( &struct_name, &static_call.method, arg_type, static_call.span, + Some(&receiver_key), ) { return TypeTable::ERROR; @@ -2237,7 +2246,13 @@ impl Elaborator<'_, H> { let param_is_mut = struct_name_for_lookup .as_deref() - .map(|name| self.lookup_static_method_param_is_mut(name, &static_call.method)) + .map(|name| { + self.lookup_static_method_param_is_mut( + name, + &static_call.method, + struct_key_for_lookup.as_ref(), + ) + }) .unwrap_or_default(); // Build method_info with base struct name and trait name (if applicable) @@ -2254,12 +2269,12 @@ impl Elaborator<'_, H> { // The selection covers trait impls only; an inherent static has none // and reaches the index instead. - if let Some(method_ast_id) = selected.as_ref().and_then(|r| r.method_id).or_else(|| { + if let Some(method_def) = selected.as_ref().and_then(|r| r.method_id).or_else(|| { let receiver = self.impl_target_of(target_type_id, &crate::name::DeclName::new(&struct_name)); self.static_method_decl_id(&receiver, &static_call.method) }) { - self.record_reference_to_def(static_call.method_id, method_ast_id); + self.record_reference_to_decl(static_call.method_id, method_def); } let func_ref = FunctionRef { @@ -2538,12 +2553,7 @@ impl Elaborator<'_, H> { // declares the method. The header alone would match an instance // method of the same name; both indices together will not. .filter(|(_, b)| { - let Some(header) = self - .tysys - .trait_env - .impl_headers - .get(&(b.module.clone(), b.ast_id)) - else { + let Some(header) = self.tysys.trait_env.impl_headers.get(&b.def) else { return false; }; self.tysys @@ -2556,18 +2566,14 @@ impl Elaborator<'_, H> { .is_some_and(|entries| { entries.iter().any(|e| { e.name == method_name - && header.methods.iter().any(|m| m.ast_id == e.method_id) + && header.methods.iter().any(|m| m.def == e.method_id) }) }) }) // The trait comes off the impl's own header, so the blanket // index's bare-name key never reaches a mangled name. .filter_map(|(_, b)| { - let header = self - .tysys - .trait_env - .impl_headers - .get(&(b.module.clone(), b.ast_id))?; + let header = self.tysys.trait_env.impl_headers.get(&b.def)?; Some(( self.tysys .trait_env @@ -2771,7 +2777,7 @@ impl Elaborator<'_, H> { &mut self, struct_name: &str, method_name: &str, - static_key_hint: Option<&crate::elaborator::trait_env::ImplTargetKey>, + target_hint: Option<&crate::elaborator::trait_env::ImplTargetKey>, ) -> Vec { // O(1) lookup via pre-built static method index. The index is // keyed by the receiver's canonical decl key so two same-named @@ -2780,9 +2786,7 @@ impl Elaborator<'_, H> { // (it threads through the `TypeId`'s module source and so // distinguishes `CounterA::make` from `CounterB::make` even // though both alias the same bare name `"Counter"`). - let static_key = static_key_hint - .cloned() - .unwrap_or_else(|| self.impl_target(struct_name)); + let static_key = self.static_receiver_key(struct_name, target_hint); // Carry the impl's defining module out of the index alongside // the AST so the per-param elaborator can swap into its perspective — // a static method's signature references types the impl module @@ -2868,51 +2872,61 @@ impl Elaborator<'_, H> { /// the same order as [`Self::lookup_static_method_param_types_keyed`]. /// Returns `(param_name, default_expr)` pairs; `default_expr` is `None` for /// parameters without a declared default. - pub(super) fn lookup_static_method_param_defaults_keyed( + pub(super) fn lookup_static_method_param_defaults( &mut self, struct_name: &str, method_name: &str, - static_key_hint: Option<&crate::elaborator::trait_env::ImplTargetKey>, + target_hint: Option<&crate::elaborator::trait_env::ImplTargetKey>, ) -> Vec<(String, Option)> { - let static_key = static_key_hint - .cloned() - .unwrap_or_else(|| self.impl_target(struct_name)); + let static_key = self.static_receiver_key(struct_name, target_hint); // Names and defaults come out of the same record, so their order // matches the parameter types by construction. - let indexed = self - .unique_static_method_sig(&static_key, method_name) - .map(|sig| crate::elaborator::sig::Param::named_defaults(&sig.params)); - if let Some(defaults) = indexed { - return defaults; - } + self.unique_static_method_sig(&static_key, method_name) + .map(|sig| crate::elaborator::sig::Param::named_defaults(&sig.params)) + .unwrap_or_default() + } - Vec::new() + /// The receiver a static lookup keys on: the key its caller already + /// resolved, else the written name over the walking module's frame. + fn static_receiver_key( + &self, + struct_name: &str, + target_hint: Option<&ImplTargetKey>, + ) -> ImplTargetKey { + target_hint + .cloned() + .unwrap_or_else(|| self.impl_target(struct_name)) } - /// Keys of the *trait* impl blocks whose target head is written - /// `struct_name`, current-module-first. A written name reaches two receiver - /// namespaces: usually a declaration, but an impl binding it as its own type - /// parameter (`impl Trait for V`) keys under that binder instead. - /// Both are searched in the current module, only the declaration namespace - /// outside it. Consumers re-check the impl's spelling. - fn trait_impl_keys_current_first(&self, struct_name: &str) -> Vec<(ModuleSource, AstId)> { + /// The *trait* impl blocks a receiver written `struct_name` reaches, + /// current-module-first, with the declared name their heads must spell. + /// + /// A receiver reaches two namespaces: its declaration, and an impl binding + /// the name as its own type parameter (`impl Trait for V`), which + /// keys under that binder. Both are searched in the current module, only + /// the declaration namespace outside it. + fn trait_impls_for_receiver( + &self, + struct_name: &str, + target_hint: Option<&ImplTargetKey>, + ) -> (Vec, String) { + let defs = self.tysys.resolutions.defs(); + let target = self.static_receiver_key(struct_name, target_hint); + let declared_name = target.type_name(defs).unwrap_or(struct_name).to_string(); + let env = &self.tysys.trait_env; - let declared = env.entries_by_receiver_vec( - &self - .impl_target(struct_name) - .receiver(self.tysys.resolutions.defs()), - ); - let binder = env.entries_by_receiver_vec(&Receiver::Type(FqTypeName::binder(struct_name))); - let is_current = - |(module, _): &&(ModuleSource, AstId)| *module == self.current_module_source; - let mut keys: Vec<(ModuleSource, AstId)> = declared + let declared = env.entries_by_receiver_vec(&target.receiver(defs)); + let binder = + env.entries_by_receiver_vec(&Receiver::Type(FqTypeName::binder(&declared_name))); + let is_current = |k: &&crate::defs::DefId| *defs.module(**k) == self.current_module_source; + let mut keys: Vec = declared .iter() .chain(binder.iter()) .filter(is_current) - .cloned() + .copied() .collect(); - keys.extend(declared.iter().filter(|k| !is_current(k)).cloned()); - keys + keys.extend(declared.iter().filter(|k| !is_current(k)).copied()); + (keys, declared_name) } /// Canonical signatures of the methods named `method_name` declared on @@ -2935,9 +2949,9 @@ impl Elaborator<'_, H> { let sig = self .tysys .signatures - .method_sig(method.ast_id) + .method_sig(method.def) .expect("the decl pass records every impl-declared method's signature"); - if key.0 == self.current_module_source { + if *self.tysys.resolutions.defs().module(*key) == self.current_module_source { current.push(sig); } else { others.push(sig); @@ -2957,16 +2971,6 @@ impl Elaborator<'_, H> { .unwrap_or_default() } - /// Look up whether each parameter of a static method is `mut`. - /// Returns empty vec (conservative) for unknown methods. - pub(super) fn lookup_static_method_param_is_mut( - &self, - struct_name: &str, - method_name: &str, - ) -> Vec { - self.lookup_static_method_param_is_mut_keyed(struct_name, method_name, None) - } - /// The return type every static method under this name agrees on, `None` when /// they disagree. An overload set still answers: every `From` impl returns the /// receiver, so which one this call reaches cannot change the result. @@ -3012,7 +3016,7 @@ impl Elaborator<'_, H> { /// The declared type-param slots of a static method, keyed like /// [`Self::lookup_static_method_param_types_keyed`]. - pub(super) fn lookup_static_method_slots_keyed( + pub(super) fn lookup_static_method_slots( &self, method_name: &str, static_key: &crate::elaborator::trait_env::ImplTargetKey, @@ -3022,17 +3026,16 @@ impl Elaborator<'_, H> { .unwrap_or_default() } - /// Like [`Self::lookup_static_method_param_is_mut`] but takes a pre-resolved - /// receiver key, which a namespace member's bare spelling cannot reach. - pub(super) fn lookup_static_method_param_is_mut_keyed( + /// Whether each parameter of a static method is `mut`, empty for an unknown + /// method. The receiver key is pre-resolved where the caller holds one — a + /// namespace member's bare spelling cannot reach it. + pub(super) fn lookup_static_method_param_is_mut( &self, struct_name: &str, method_name: &str, - static_key_hint: Option<&crate::elaborator::trait_env::ImplTargetKey>, + target_hint: Option<&crate::elaborator::trait_env::ImplTargetKey>, ) -> Vec { - let type_target = static_key_hint - .cloned() - .unwrap_or_else(|| self.impl_target(struct_name)); + let type_target = self.static_receiver_key(struct_name, target_hint); self.impl_method_sigs(&type_target, method_name) .into_iter() .find(|sig| sig.self_kind == ast::SelfKind::None) @@ -3078,7 +3081,7 @@ impl Elaborator<'_, H> { struct_name: &str, method_name: &str, ) -> Option { - self.locate_static_method_impl(struct_name, method_name, None) + self.locate_static_method_impl(struct_name, method_name, None, None) .and_then(|r| r.trait_name) } @@ -3107,8 +3110,10 @@ impl Elaborator<'_, H> { method_name: &str, arg_type: &str, span: Span, + target_hint: Option<&ImplTargetKey>, ) -> bool { - let (candidates, has_blanket) = self.conversion_impl_survey(struct_name, method_name); + let (candidates, has_blanket) = + self.conversion_impl_survey(struct_name, method_name, target_hint); if has_blanket { let _ = self.emit(TypeError::UnsupportedBlanketConversion { trait_name: self.conversion_trait_name(method_name), @@ -3146,14 +3151,15 @@ impl Elaborator<'_, H> { span: Span, ctx: &mut FunctionContext, param_types: &mut Vec, + target_hint: Option<&ImplTargetKey>, ) -> bool { if (method_name != "from" && method_name != "try_from") - || self.has_inherent_static_method(recv_name, method_name) + || self.has_inherent_static_method(recv_name, method_name, target_hint) { return false; } let class = self.synthesize_arg_class(arg, ctx); - match self.conversion_preselect(recv_name, method_name, &class) { + match self.conversion_preselect(recv_name, method_name, &class, target_hint) { ConversionPreselect::Selected(source) => { *param_types = vec![source]; false @@ -3176,11 +3182,7 @@ impl Elaborator<'_, H> { /// whether each takes `self`, so the question is answered without an /// impl-block AST — and keyed canonically, so two modules' same-named /// types cannot answer for each other. - fn keys_declare_static_method( - &self, - keys: &[(ModuleSource, crate::ast::AstId)], - method_name: &str, - ) -> bool { + fn keys_declare_static_method(&self, keys: &[crate::defs::DefId], method_name: &str) -> bool { keys.iter().any(|key| { self.tysys .trait_env @@ -3193,7 +3195,7 @@ impl Elaborator<'_, H> { && self .tysys .signatures - .method_sig(m.ast_id) + .method_sig(m.def) .is_some_and(|sig| sig.self_kind == ast::SelfKind::None) }) }) @@ -3203,11 +3205,14 @@ impl Elaborator<'_, H> { /// of this name. A conversion-call guard needs the distinction: a trait /// lookup returning `None` is a failure only when no inherent static can /// answer instead. - pub(super) fn has_inherent_static_method(&self, struct_name: &str, method_name: &str) -> bool { - let keys = self - .tysys - .trait_env - .inherent_impl_keys(&self.impl_target(struct_name)); + pub(super) fn has_inherent_static_method( + &self, + struct_name: &str, + method_name: &str, + target_hint: Option<&ImplTargetKey>, + ) -> bool { + let target = self.static_receiver_key(struct_name, target_hint); + let keys = self.tysys.trait_env.inherent_impl_keys(&target); self.keys_declare_static_method(&keys, method_name) } @@ -3221,12 +3226,14 @@ impl Elaborator<'_, H> { struct_name: &str, method_name: &str, class: &super::synth::ArgClass, + target_hint: Option<&ImplTargetKey>, ) -> ConversionPreselect { use super::synth::ArgClass; if matches!(class, ArgClass::Opaque(_)) { return ConversionPreselect::Pass; } - let (candidates, _has_blanket) = self.conversion_impl_survey(struct_name, method_name); + let (candidates, _has_blanket) = + self.conversion_impl_survey(struct_name, method_name, target_hint); let admitted: Vec = candidates .into_iter() .filter(|c| { @@ -3258,6 +3265,7 @@ impl Elaborator<'_, H> { &self, struct_name: &str, method_name: &str, + target_hint: Option<&ImplTargetKey>, ) -> (Vec, bool) { let from_trait_name = self .tysys @@ -3267,13 +3275,15 @@ impl Elaborator<'_, H> { .to_string(); let mut candidates: Vec = Vec::new(); let mut has_blanket = false; - for (module, impl_id) in self.trait_impl_keys_current_first(struct_name) { - let header = &self.tysys.trait_env.impl_headers[&(module.clone(), impl_id)]; + let (impl_defs, declared_name) = self.trait_impls_for_receiver(struct_name, target_hint); + for impl_def in impl_defs { + let header = &self.tysys.trait_env.impl_headers[&impl_def]; + let module = self.tysys.resolutions.defs().module(impl_def).clone(); let Some(trait_type) = header.trait_type.as_ref() else { continue; }; let base = super::trait_env::get_type_name_static(trait_type); - if super::trait_env::get_type_name_static(&header.ty) != struct_name + if self.impl_head_decl_name(header, &module) != declared_name || (base != from_trait_name && base != "TryFrom") || !header.methods.iter().any(|m| m.name == method_name) { @@ -3311,7 +3321,7 @@ impl Elaborator<'_, H> { let source = *self .tysys .signatures - .impl_sig(impl_id) + .impl_sig(impl_def) .expect("the decl pass records every impl block's declaration facts") .trait_type_args .first() @@ -3339,6 +3349,21 @@ impl Elaborator<'_, H> { ) } + /// An impl header's target head as a declaration name, resolved through the + /// impl's own imports — unless its type parameters bind the spelling, which + /// shadows them. + fn impl_head_decl_name( + &self, + header: &super::trait_env::ImplHeader, + impl_module: &ModuleSource, + ) -> String { + let head = super::trait_env::get_type_name_static(&header.ty); + if header.type_params.iter().any(|p| p.name == head) { + return head; + } + self.import_original_name(&head, impl_module) + } + /// Whether `rendered` names `param` as a whole segment — the spelling-level /// stand-in for "this type mentions the impl's type parameter". fn mentions_type_param(rendered: &str, param: &str) -> bool { @@ -3352,7 +3377,9 @@ impl Elaborator<'_, H> { struct_name: &str, method_name: &str, arg_type_name: Option<&str>, + target_hint: Option<&ImplTargetKey>, ) -> Option { + let (impl_defs, declared_name) = self.trait_impls_for_receiver(struct_name, target_hint); let from_trait_name = self .tysys .type_table @@ -3448,15 +3475,15 @@ impl Elaborator<'_, H> { !is_from_or_try_from(&base) }; - // Returns the trait the impl names and the node declaring the method - // there — the identity of what this selection picked, so a caller - // recording a use→def edge names the impl the argument chose rather - // than the receiver's first same-named method. + // Returns the trait the impl names and the method it declares there — + // the identity of what this selection picked, so a caller recording a + // use→def edge names the impl the argument chose rather than the + // receiver's first same-named method. let check_impl = |header: &super::trait_env::ImplHeader, impl_module: &ModuleSource| - -> Option<(crate::name::FqTraitName, AstId)> { + -> Option<(crate::name::FqTraitName, crate::defs::DefId)> { let trait_type = header.trait_type.as_ref()?; - if super::trait_env::get_type_name_static(&header.ty) != struct_name + if self.impl_head_decl_name(header, impl_module) != declared_name || !matches_arg_type(trait_type, &header.ty, impl_module, &header.type_params) { return None; @@ -3465,10 +3492,10 @@ impl Elaborator<'_, H> { let sig = self .tysys .signatures - .method_sig(method.ast_id) + .method_sig(method.def) .expect("the decl pass records every impl-declared method's signature"); if sig.self_kind == ast::SelfKind::None { - return Some((resolve_trait_name(header)?, method.ast_id)); + return Some((resolve_trait_name(header)?, method.def)); } } // Fall back to the trait declaration's default methods: when @@ -3484,13 +3511,14 @@ impl Elaborator<'_, H> { && method.default_body.is_some() && method.sig.self_kind == ast::SelfKind::None { - return Some((resolve_trait_name(header)?, method.sig.ast_id)); + return Some((resolve_trait_name(header)?, method.sig.def)); } None }; - for (module_source, impl_id) in self.trait_impl_keys_current_first(struct_name) { - let header = &self.tysys.trait_env.impl_headers[&(module_source.clone(), impl_id)]; + for impl_def in impl_defs { + let header = &self.tysys.trait_env.impl_headers[&impl_def]; + let module_source = self.tysys.resolutions.defs().module(impl_def).clone(); if let Some((trait_name, method_id)) = check_impl(header, &module_source) { return Some(StaticMethodRef::new( module_source, @@ -3641,7 +3669,7 @@ impl Elaborator<'_, H> { // `Type::method` must still resolve. `locate_static_method_impl` // applies the same fallback to find the trait name and module. if self - .locate_static_method_impl(struct_name, method_name, None) + .locate_static_method_impl(struct_name, method_name, None, None) .is_some() { return true; @@ -3753,10 +3781,20 @@ impl Elaborator<'_, H> { } else { None }; + // A newtype's static call dispatches to its base, whose name is not + // the caller's to resolve — that frame can hold a same-named + // declaration of its own. + let receiver_key = newtype_dispatch.as_ref().map(|(_, base_type_id, _)| { + self.impl_target_of( + *base_type_id, + &crate::name::DeclName::new(&actual_struct_name), + ) + }); let resolved = self.locate_static_method_impl( &actual_struct_name, method_name, arg_type_hint.as_deref(), + receiver_key.as_ref(), ); // The expected type that shaped the argument came from // `lookup_static_method_param_types_keyed`, which keys on (receiver, @@ -3766,8 +3804,18 @@ impl Elaborator<'_, H> { // disagreement is reported here instead of ICE-ing there. if resolved.is_none() && let Some(arg_type) = arg_type_hint.as_deref() - && !self.has_inherent_static_method(&actual_struct_name, method_name) - && self.report_unmatched_conversion(&actual_struct_name, method_name, arg_type, span) + && !self.has_inherent_static_method( + &actual_struct_name, + method_name, + receiver_key.as_ref(), + ) + && self.report_unmatched_conversion( + &actual_struct_name, + method_name, + arg_type, + span, + receiver_key.as_ref(), + ) { return placeholder(TypeTable::ERROR, span); } @@ -3825,18 +3873,27 @@ impl Elaborator<'_, H> { }) }; - let param_is_mut = self.lookup_static_method_param_is_mut(&actual_struct_name, method_name); + let param_is_mut = self.lookup_static_method_param_is_mut( + &actual_struct_name, + method_name, + receiver_key.as_ref(), + ); - let param_defaults = - self.lookup_static_method_param_defaults_keyed(&actual_struct_name, method_name, None); + let param_defaults = self.lookup_static_method_param_defaults( + &actual_struct_name, + method_name, + receiver_key.as_ref(), + ); - // Propagate #[cm("...")] from resource static methods - // The selected method's own declaration knows its owner, so the - // resource is reached through it rather than through its spelling. + // Propagate #[cm("...")] from resource static methods. A method the + // *resource* declares names it as its own owner; one an `impl` block + // declares owns to the block, which names no resource, so the spelling + // answers there as it did before impl methods were identified. + let defs = self.tysys.resolutions.defs(); let cm_owner = method_ref .method_id - .and_then(|id| self.tysys.resolutions.defs().of_ast_id(id)) - .and_then(|method| self.tysys.resolutions.defs().parent(method)) + .and_then(|method| defs.parent(method)) + .filter(|owner| defs.kind(*owner) == crate::defs::DefKind::Resource) .or_else(|| self.decl_key_or_local(&actual_struct_name)); let cm_name = self.lookup_resource_static_cm(cm_owner, method_name); @@ -3866,8 +3923,11 @@ impl Elaborator<'_, H> { .zip(param_is_mut.iter().copied().chain(std::iter::repeat(false))) .map(|(_, is_mut)| is_mut) .collect(); - let param_types = - self.lookup_static_method_param_types_keyed(&actual_struct_name, method_name, None); + let param_types = self.lookup_static_method_param_types_keyed( + &actual_struct_name, + method_name, + receiver_key.as_ref(), + ); self.sem.types.static_method_dispatch.insert( call_id, super::sem::types::StaticMethodDispatch { diff --git a/wado-compiler/src/elaborator/method_lookup.rs b/wado-compiler/src/elaborator/method_lookup.rs index e793d6b5eb..3e6ed896ac 100644 --- a/wado-compiler/src/elaborator/method_lookup.rs +++ b/wado-compiler/src/elaborator/method_lookup.rs @@ -6,9 +6,10 @@ use std::sync::Arc; use crate::hashmap::{IndexMap, IndexSet}; -use crate::ast::{self, AstId, BinaryOp, Expr, Type}; +use crate::ast::{self, BinaryOp, Expr, Type}; use crate::compiler_host::CompilerHost; use crate::compiler_item::CompilerItem; +use crate::defs::DefId; use crate::module_source::ModuleSource; use crate::name::{LocalMethodName, MethodName, RefKind}; use crate::tir::{ @@ -35,10 +36,10 @@ use super::util::placeholder; pub(super) const REPLACE_ON_ASSIGN_PLACE: &str = "a field or element of a replace-on-assign type (primitive, enum, flags, fn); \ use the containing value's reference directly"; -/// Lightweight reference to an impl block. Stores `(module_source, -/// item_id)` and resolves to the block's digested [`ImplHeader`] via -/// [`impl_header`]. Dispatch cannot reach the impl AST at all. -struct ImplBlockRef(ModuleSource, AstId); +/// Lightweight reference to an impl block: its identity, resolving to the +/// block's digested [`ImplHeader`] via [`impl_header`]. Dispatch cannot reach +/// the impl AST at all. +struct ImplBlockRef(DefId); /// The digested header of the impl block `r` points at. Borrowed from the /// caller's `TraitEnv` handle rather than from `&self`, so the header stays @@ -50,7 +51,7 @@ struct ImplBlockRef(ModuleSource, AstId); fn impl_header<'a>(trait_env: &'a TraitEnv, r: &ImplBlockRef) -> &'a ImplHeader { trait_env .impl_headers - .get(&(r.0.clone(), r.1)) + .get(&r.0) .expect("every indexed impl block has an ImplHeader") } @@ -59,7 +60,7 @@ impl Elaborator<'_, H> { fn impl_sig(&self, r: &ImplBlockRef) -> &super::sig::ImplSig { self.tysys .signatures - .impl_sig(r.1) + .impl_sig(r.0) .expect("the decl pass records every impl block's declaration facts") } } @@ -333,7 +334,7 @@ impl TypeSystem { impl Elaborator<'_, H> { /// Get the module source for an `ImplBlockRef`. fn impl_block_module_source(&self, r: &ImplBlockRef) -> ModuleSource { - r.0.clone() + self.tysys.resolutions.defs().module(r.0).clone() } /// Collect trait impl block references for a given type name. @@ -349,7 +350,7 @@ impl Elaborator<'_, H> { .get(entry) .is_some_and(|h| h.trait_name.is_some()) { - refs.push(ImplBlockRef(entry.0.clone(), entry.1)); + refs.push(ImplBlockRef(*entry)); } } } @@ -369,7 +370,7 @@ impl Elaborator<'_, H> { .get(entry) .is_some_and(|h| h.trait_name.is_some()) { - refs.push(ImplBlockRef(entry.0.clone(), entry.1)); + refs.push(ImplBlockRef(*entry)); } } } @@ -407,7 +408,7 @@ impl Elaborator<'_, H> { continue; } let impl_sig = signatures - .impl_sig(impl_ref.1) + .impl_sig(impl_ref.0) .expect("the decl pass records every impl block's declaration facts") .instantiate(&self.tysys.type_table, concrete_type_args); let declared = self @@ -703,7 +704,7 @@ impl Elaborator<'_, H> { let elems = type_args; if method_name == "len" { return Some(MethodInfo { - method_ast_id: None, + method_def: None, return_type: TypeTable::I32, self_kind: ast::SelfKind::Ref, param_types: vec![], @@ -746,7 +747,7 @@ impl Elaborator<'_, H> { } let return_type = self.tysys.type_table.borrow_mut().make_tuple(transposed); return Some(MethodInfo { - method_ast_id: None, + method_def: None, return_type, self_kind: ast::SelfKind::Ref, param_types: vec![], @@ -870,14 +871,14 @@ impl Elaborator<'_, H> { // Coherence lets any same-package module host an `impl `. if struct_module_source.is_some() { - let entries: Vec<(ModuleSource, AstId)> = self.tysys.trait_env.inherent_impl_keys( + let entries: Vec = self.tysys.trait_env.inherent_impl_keys( &self.impl_target_of(base_type_id, &crate::name::DeclName::new(&struct_name)), ); // The receiver's own declaration, which is what an impl header // targeting it must name. let receiver_decl = self.tysys.type_table.borrow().nominal_def(base_type_id); - for (impl_module, item_id) in &entries { - let impl_ref = ImplBlockRef(impl_module.clone(), *item_id); + for entry in &entries { + let impl_ref = ImplBlockRef(*entry); let trait_env = Arc::clone(&self.tysys.trait_env); let header = impl_header(&trait_env, &impl_ref); // The header names its target at a site of its own, answered @@ -908,11 +909,11 @@ impl Elaborator<'_, H> { } if struct_module_source.is_none() { - let entries: Vec<(ModuleSource, AstId)> = self.tysys.trait_env.inherent_impl_keys( + let entries: Vec = self.tysys.trait_env.inherent_impl_keys( &self.impl_target_of(base_type_id, &crate::name::DeclName::new(&struct_name)), ); - for (search_module_source, item_id) in &entries { - let impl_ref = ImplBlockRef(search_module_source.clone(), *item_id); + for entry in &entries { + let impl_ref = ImplBlockRef(*entry); let trait_env = Arc::clone(&self.tysys.trait_env); let header = impl_header(&trait_env, &impl_ref); if self.get_type_name(&header.ty) != struct_name @@ -994,9 +995,9 @@ impl Elaborator<'_, H> { let signatures = Rc::clone(&self.tysys.signatures); let header = impl_header(&trait_env, impl_ref); let method_header = header.methods.iter().find(|m| m.name == method_name)?; - let sig = signatures.method_sig(method_header.ast_id)?; + let sig = signatures.method_sig(method_header.def)?; let impl_sig = signatures - .impl_sig(impl_ref.1) + .impl_sig(impl_ref.0) .expect("the decl pass records every impl block's declaration facts"); let slots = impl_sig.slots(&self.tysys.type_table, receiver_type_args.unwrap_or(&[])); @@ -1004,7 +1005,7 @@ impl Elaborator<'_, H> { let first_value = sig.first_value_param().min(instantiated.param_types.len()); Some(MethodInfo { - method_ast_id: Some(sig.ast_id), + method_def: Some(sig.def), return_type: instantiated.return_type, self_kind: sig.self_kind, param_types: instantiated.param_types[first_value..].to_vec(), @@ -1014,7 +1015,7 @@ impl Elaborator<'_, H> { is_ref_impl: false, method_type_param_ids: sig.own_type_param_ids(), method_own_params: sig.own_params.clone(), - impl_module: Some(impl_ref.0.clone()), + impl_module: Some(self.impl_block_module_source(impl_ref)), from_concrete_impl: self.impl_is_concrete_instantiation(&header.ty), param_defaults: sig.params.iter().map(|p| p.default.clone()).collect(), param_names: super::sig::Param::names(&sig.params), @@ -1061,11 +1062,10 @@ impl Elaborator<'_, H> { method_name: &str, ) -> Option<(crate::defs::DefId, super::sig::MethodSig)> { self.resource_chain_of(def).into_iter().find_map(|current| { - let info = self.tysys.all_resource_types.get(¤t)?; let sig = self .tysys .signatures - .resource_method_sig(info.defined_at, method_name)?; + .resource_method_sig(current, method_name)?; (sig.self_kind != ast::SelfKind::None).then(|| (current, sig.clone())) }) } @@ -1079,12 +1079,7 @@ impl Elaborator<'_, H> { method_name: &str, ) -> Option { for impl_ref in self.collect_trait_impl_refs_multi(std::slice::from_ref(type_key)) { - let Some(header) = self - .tysys - .trait_env - .impl_headers - .get(&(impl_ref.0.clone(), impl_ref.1)) - else { + let Some(header) = self.tysys.trait_env.impl_headers.get(&impl_ref.0) else { continue; }; if header.methods.iter().any(|m| m.name == method_name) @@ -1103,11 +1098,10 @@ impl Elaborator<'_, H> { method_name: &str, receiver_type_args: Option<&[TypeId]>, ) -> Option { - let decl_id = self.tysys.all_resource_types.get(&def)?.defined_at; let sig = self .tysys .signatures - .resource_method_sig(decl_id, method_name)? + .resource_method_sig(def, method_name)? .clone(); if sig.self_kind == ast::SelfKind::None { return None; @@ -1120,7 +1114,7 @@ impl Elaborator<'_, H> { let method_type_param_ids = sig.own_type_param_ids(); Some(MethodInfo { - method_ast_id: Some(sig.ast_id), + method_def: Some(sig.def), return_type: instantiated.return_type, self_kind: sig.self_kind, param_types: instantiated.param_types[first_value..].to_vec(), @@ -1650,19 +1644,19 @@ impl Elaborator<'_, H> { // Blanket impl fallback: check `impl Trait for T` where the receiver // type satisfies the bound. e.g., `impl IntoIterator for I` matches // any concrete type that implements Iterator. Snapshot the value blankets - // (module, ast id, bound names) so the per-bound checks below borrow `self` + // (block, bound names) so the per-bound checks below borrow `self` // without holding a `trait_env` borrow. - let value_blankets: Vec<(ModuleSource, AstId, Vec)> = self + let value_blankets: Vec<(DefId, Vec)> = self .tysys .trait_env .blanket_impls .values() .flatten() .filter(|b| b.receiver == super::trait_env::BlanketReceiver::Value) - .map(|b| (b.module.clone(), b.ast_id, b.bounds.clone())) + .map(|b| (b.def, b.bounds.clone())) .collect(); let type_lookup = self.type_lookup(); - for (module, ast_id, bounds) in &value_blankets { + for (blanket, bounds) in &value_blankets { // Gate on all bounds. The receiver-`TypeId` check is preferred: // it recognises synthesized bounds (`ReflectStruct`, `Default`) with no // explicit `impl`, which the name-based lookup misses. A viable @@ -1699,7 +1693,7 @@ impl Elaborator<'_, H> { .tysys .blanket_assoc_constraints_hold(receiver_type_id, bounds) { - impl_refs.push(ImplBlockRef(module.clone(), *ast_id)); + impl_refs.push(ImplBlockRef(*blanket)); } } impl_refs @@ -1863,14 +1857,15 @@ impl Elaborator<'_, H> { // frame exactly when a target argument names a type the impl's module // cannot see, and the target then carries a slot this filter believes // is concrete. + let impl_module = self.impl_block_module_source(impl_ref); let is_target_slot = |name: &str| { self.tysys - .is_impl_target_param(&impl_ref.0, &header.type_params, name) + .is_impl_target_param(&impl_module, &header.type_params, name) }; let is_blanket_tp = matches!(&header.ty, Type::Named(n) if is_target_slot(&n.name)); let generic_is_parametric = matches!(&header.ty, Type::Generic(g) if g.args.iter().any(|a| matches!(a, Type::Named(n) if is_target_slot(&n.name)))); - let skip_filter = !header.type_params.is_empty() + let skip_filter = !header.is_concrete() || is_blanket_tp || matches!(&header.ty, Type::Reference(_) | Type::MutReference(_)) || generic_is_parametric; @@ -2181,7 +2176,7 @@ impl Elaborator<'_, H> { // this query, resolved it. let signatures = Rc::clone(&scope.tysys.signatures); let impl_sig = signatures - .impl_sig(impl_ref.1) + .impl_sig(impl_ref.0) .expect("the decl pass records every impl block's declaration facts") .instantiate_slots(&scope.tysys.type_table, &impl_slots); scope.annotate_ctx.trait_ctx.assoc_type_bindings.extend( @@ -2221,7 +2216,7 @@ impl Elaborator<'_, H> { let sig = scope .tysys .signatures - .method_sig(m.ast_id) + .method_sig(m.def) .expect("the decl pass records every impl-declared method's signature") .clone(); (sig, m.type_params.clone()) @@ -2239,7 +2234,7 @@ impl Elaborator<'_, H> { // the spelling it wrote, which is how an erroneous block reaches a // lookup at all; a candidate built without an identity keys on nothing. let Some(trait_decl) = signatures - .impl_sig(impl_ref.1) + .impl_sig(impl_ref.0) .expect("the decl pass records every impl block's declaration facts") .trait_decl else { @@ -2353,7 +2348,7 @@ impl Elaborator<'_, H> { trait_decl, trait_args: trait_args.clone(), method_info: MethodInfo { - method_ast_id: Some(method_sig.ast_id), + method_def: Some(method_sig.def), return_type, self_kind, param_types, @@ -2415,7 +2410,7 @@ impl Elaborator<'_, H> { trait_decl, trait_args: trait_args.clone(), method_info: MethodInfo { - method_ast_id: Some(default_method.sig.ast_id), + method_def: Some(default_method.sig.def), return_type: instantiated.return_type, self_kind, param_types: instantiated.param_types[first_value_param..].to_vec(), @@ -3044,7 +3039,7 @@ impl Elaborator<'_, H> { // type arguments is what the by-name re-resolution below used // to approximate. let method_header = header.methods.iter().find(|m| m.name == method_name)?; - let method_sig = s.tysys.signatures.method_sig(method_header.ast_id)?; + let method_sig = s.tysys.signatures.method_sig(method_header.def)?; let self_kind = method_sig.self_kind; let rhs_index = usize::from(self_kind != ast::SelfKind::None); let rhs_type = method_sig @@ -3075,6 +3070,7 @@ impl Elaborator<'_, H> { .unwrap_or(base_type_id); Some(ArithmeticTraitInfo { + impl_def: impl_ref.0, output_type, self_kind, impl_module_source: header.module.clone(), @@ -3117,8 +3113,8 @@ impl Elaborator<'_, H> { &self, callee: &super::callee::CalleeRef, ) -> Vec { - let callee_module = &callee.module; - let func_name = callee.name.as_str(); + let callee_module = callee.module(); + let func_name = callee.name(); let fn_type_params = &self.tysys.trait_env.function_type_params; // Entry-point callees are looked up in the current module's functions first. if callee_module.is_entry_point() @@ -3205,11 +3201,7 @@ impl Elaborator<'_, H> { // the indexing types come from the impl's associated-type // bindings. let method_header = header.methods.iter().find(|m| m.name == method_name)?; - let self_kind = s - .tysys - .signatures - .method_sig(method_header.ast_id)? - .self_kind; + let self_kind = s.tysys.signatures.method_sig(method_header.def)?.self_kind; let impl_source = s.impl_block_module_source(impl_ref); let assoc_type = impl_sig @@ -3338,7 +3330,7 @@ impl Elaborator<'_, H> { } let MethodInfo { - method_ast_id: _, + method_def: _, return_type, self_kind, param_types, diff --git a/wado-compiler/src/elaborator/operators.rs b/wado-compiler/src/elaborator/operators.rs index 5725b3d431..50d95752ae 100644 --- a/wado-compiler/src/elaborator/operators.rs +++ b/wado-compiler/src/elaborator/operators.rs @@ -528,6 +528,7 @@ impl Elaborator<'_, H> { let resolved = ResolvedTraitMethod { trait_name: eq_trait_name, method_name: "eq".to_string(), + impl_def: None, impl_name: name.clone(), impl_type_id: None, self_kind: info.self_kind, @@ -569,6 +570,7 @@ impl Elaborator<'_, H> { let resolved = ResolvedTraitMethod { trait_name: ord_trait_name, method_name: "cmp".to_string(), + impl_def: None, impl_name: name.clone(), impl_type_id: None, self_kind: info.self_kind, @@ -680,6 +682,7 @@ impl Elaborator<'_, H> { let resolved = ResolvedTraitMethod { trait_name: trait_info.trait_name, method_name: method_name.to_string(), + impl_def: Some(trait_info.impl_def), impl_name, impl_type_id: Some(impl_type_id), self_kind: trait_info.self_kind, @@ -722,6 +725,7 @@ impl Elaborator<'_, H> { let resolved = ResolvedTraitMethod { trait_name: found_trait, method_name: method_name.to_string(), + impl_def: None, impl_name: name.clone(), impl_type_id: None, self_kind: info.self_kind, @@ -787,6 +791,7 @@ impl Elaborator<'_, H> { let resolved = ResolvedTraitMethod { trait_name: found_trait, method_name: shift_method.to_string(), + impl_def: None, impl_name: name.clone(), impl_type_id: None, self_kind: info.self_kind, @@ -870,6 +875,7 @@ impl Elaborator<'_, H> { let resolved = ResolvedTraitMethod { trait_name: trait_info.trait_name, method_name: method_name.to_string(), + impl_def: Some(trait_info.impl_def), impl_name, impl_type_id: Some(impl_type_id), self_kind: trait_info.self_kind, @@ -1161,6 +1167,7 @@ impl Elaborator<'_, H> { let resolved = ResolvedTraitMethod { trait_name: found_trait, method_name: method_name.to_string(), + impl_def: None, impl_name: name.clone(), impl_type_id: None, self_kind: info.self_kind, @@ -1960,20 +1967,30 @@ impl Elaborator<'_, H> { ); method_info.is_type_param_receiver = resolved.is_type_param_receiver; - // Resolve the impl's module from the receiver's *actual* type, not its - // bare name: same-named structs in different modules each have their own - // operator impls (e.g. auto-derived `Eq`/`Ord`), and a by-name lookup - // would route every call to whichever registered first. `impl_name` is - // the newtype-chain link the impl was found on, so key off that link — - // peeling to the base instead would send an impl written on the newtype - // to the base's module. Fall back to the by-name lookup when the - // receiver carries no declaring module. - let module_source = self - .impl_target_decl_key(receiver.type_id, &resolved.impl_name) - .map_or_else( - || self.declaring_module_of(&resolved.impl_name), - |def| self.tysys.resolutions.defs().module(def).clone(), - ); + // Only a *concrete* block's function lives in the block's module: a + // generic block's instance is materialised in the receiver type's, + // the convention `TraitEnv::concrete_impl_module_for` encodes. + // + // Everything else answers from the receiver's newtype chain, keyed on + // the link the lookup was made on — peeling to the base would send an + // impl written on the newtype to the base's module. + let defs = self.tysys.resolutions.defs(); + let concrete_impl = resolved.impl_def.filter(|def| { + self.tysys + .trait_env + .impl_headers + .get(def) + .is_some_and(super::trait_env::ImplHeader::is_concrete) + }); + let module_source = match concrete_impl { + Some(def) => defs.module(def).clone(), + None => self + .impl_target_decl_key(receiver.type_id, &resolved.impl_name) + .map_or_else( + || self.declaring_module_of(&resolved.impl_name), + |def| defs.module(def).clone(), + ), + }; let function_ref = FunctionRef { module_source, name: mangled_method_name, diff --git a/wado-compiler/src/elaborator/orchestration.rs b/wado-compiler/src/elaborator/orchestration.rs index 5f300be4c8..9fbaf08cb8 100644 --- a/wado-compiler/src/elaborator/orchestration.rs +++ b/wado-compiler/src/elaborator/orchestration.rs @@ -1726,15 +1726,18 @@ impl<'a, H: CompilerHost> Elaborator<'a, H> { .resource_method_ids .insert(key.clone(), *method_id); } - for (ast_id, sig) in &sem.decls.impl_sigs { - signatures.impl_sigs.insert(*ast_id, sig.clone()); + for (def, sig) in &sem.decls.impl_sigs { + signatures.impl_sigs.insert(*def, sig.clone()); } for (ast_id, sig) in &sem.decls.trait_sigs { signatures.trait_sigs.insert(*ast_id, sig.clone()); } - signatures - .function_sigs - .insert(module_source.clone(), Rc::clone(&sem.decls.function_sigs)); + signatures.function_sigs.extend( + sem.decls + .function_sigs + .iter() + .map(|(def, sig)| (*def, Rc::clone(sig))), + ); signatures.globals.insert( module_source.clone(), sem.decls.current_module_globals.clone(), @@ -3756,8 +3759,6 @@ impl<'a, H: CompilerHost> Elaborator<'a, H> { if impl_block.trait_type.is_none() || impl_block.associated_types.is_empty() { continue; } - // Determine the struct name (base name without type args) - let struct_name = super::trait_env::get_type_name_static(&impl_block.ty); // The header's own reference site says which trait declares // these bindings; a block naming a trait that reaches no // declaration registers nothing. @@ -3813,16 +3814,14 @@ impl<'a, H: CompilerHost> Elaborator<'a, H> { _ => continue, }; - // The impl need not live in the type's module, so fall - // back to every loaded module before giving up. - let base_decl = { - let tt = type_table.borrow(); - tt.decl_by_name(&struct_name, module_source).or_else(|| { - modules - .keys() - .find_map(|ms| tt.decl_by_name(&struct_name, ms)) - }) - }; + // The target is named at the header's own site, like + // `trait_key` above. Rendered back to the declaring node + // because the key stays `AstId`-shaped: its readers arrive + // through `decl_of_type`, which also answers for + // monomorphized instances and `BuiltinArray`. + let base_decl = crate::resolve::head_site(&impl_block.ty) + .and_then(|site| resolutions.declared(site)) + .map(|def| resolutions.defs().ast_id(def)); if let Some(base_decl) = base_decl { type_table.borrow_mut().register_generic_assoc_type_def( base_decl, diff --git a/wado-compiler/src/elaborator/reify.rs b/wado-compiler/src/elaborator/reify.rs index 5978a3c065..58701f8e49 100644 --- a/wado-compiler/src/elaborator/reify.rs +++ b/wado-compiler/src/elaborator/reify.rs @@ -1564,7 +1564,6 @@ impl<'a, H: CompilerHost> Reify<'a, H> { }; let Some(trait_sig) = super::trait_query::trait_sig_of_with( trait_decl, - &self.tysys.resolutions, &self.tysys.trait_env, &self.tysys.signatures, ) else { diff --git a/wado-compiler/src/elaborator/sem/decls.rs b/wado-compiler/src/elaborator/sem/decls.rs index 8ffc98e791..023e3f647e 100644 --- a/wado-compiler/src/elaborator/sem/decls.rs +++ b/wado-compiler/src/elaborator/sem/decls.rs @@ -59,7 +59,7 @@ pub(crate) struct ModuleDecls { /// Canonical signatures of this module's own free functions, frozen /// behind `Rc` so the program-wide assembly and the stdlib-snapshot /// seeding share the map instead of deep-cloning every signature. - pub(crate) function_sigs: std::rc::Rc>, + pub(crate) function_sigs: std::rc::Rc>>, /// `func_name → return TypeId` for functions defined in this module. pub(crate) function_return_types: IndexMap, /// Names visible via `use` declarations in this module (the union of @@ -81,35 +81,33 @@ pub(crate) struct ModuleDecls { pub(crate) associated_constants: IndexMap<(crate::defs::DefId, String), super::super::sig::AssocConstSig>, /// Canonical signatures of this module's method declarations, keyed by - /// the method's globally-unique `AstId`. + /// the method's declaration. /// /// An `impl` method is resolved in the impl's frame — impl type params /// in their positional slots, the method's own after them, `Self` bound /// to the impl target. An `interface` / `resource` operation is resolved /// in the declaration's frame. Either way a use site instantiates /// instead of re-resolving the method AST. - pub(crate) method_sigs: IndexMap, - /// A declaration's `AstId` paired with an operation name → the - /// `AstId`, so a caller holding only a name reaches its `method_sigs` - /// entry. - pub(crate) resource_method_ids: IndexMap<(crate::ast::AstId, String), crate::ast::AstId>, + pub(crate) method_sigs: IndexMap, + /// A declaration paired with an operation name → the operation, so a + /// caller holding only a name reaches its `method_sigs` entry. + pub(crate) resource_method_ids: IndexMap<(crate::defs::DefId, String), crate::defs::DefId>, /// Facts of this module's `impl` blocks that belong to the block rather /// than to one method — its target and trait type arguments and its /// associated-type bindings — resolved once in the block's own frame and - /// keyed by the block's `AstId`. - pub(crate) impl_sigs: IndexMap, + /// keyed by the block's [`crate::defs::DefId`]. + pub(crate) impl_sigs: IndexMap, /// Facts of this module's `trait` declarations, resolved once in each - /// trait's own frame (`Self` at slot 0) and keyed by the declaration's - /// `AstId`, so a use site instantiates instead of re-resolving the trait - /// method AST. - pub(crate) trait_sigs: IndexMap, + /// trait's own frame (`Self` at slot 0) and keyed by the declaration, so + /// a use site instantiates instead of re-resolving the trait method AST. + pub(crate) trait_sigs: IndexMap, /// Resolved operation signatures of this module's `interface` and - /// `resource` declarations, keyed by the declaration's `AstId`. + /// `resource` declarations, keyed by the declaration. /// /// Resolved in the declaration's own frame — type params registered and /// `Self` constructed — which is why the body pass reads these back /// instead of resolving the same methods a second time. - pub(crate) effect_ops: IndexMap>, + pub(crate) effect_ops: IndexMap>, /// `func_name → type_params` for generic functions in this module. pub(crate) generic_function_params: IndexMap>, diff --git a/wado-compiler/src/elaborator/sem/types.rs b/wado-compiler/src/elaborator/sem/types.rs index dca6f31fd6..4dfbd2df45 100644 --- a/wado-compiler/src/elaborator/sem/types.rs +++ b/wado-compiler/src/elaborator/sem/types.rs @@ -545,7 +545,7 @@ pub(crate) struct StaticMethodDispatch { /// it after impl lookup and mangling. pub(crate) function_ref: crate::tir::FunctionRef, /// Per-argument `is_mut` flag derived from the resolved parameter - /// signature (`lookup_static_method_param_is_mut`). Reify zips this + /// signature (`lookup_static_method_param_is_mut_keyed`). Reify zips this /// with the reified argument exprs to build [`crate::tir::CallArg`]s /// with the same `is_mut` shape annotate produced. pub(crate) param_is_mut: Vec, diff --git a/wado-compiler/src/elaborator/sig.rs b/wado-compiler/src/elaborator/sig.rs index 0a1d6ce221..191b7634ce 100644 --- a/wado-compiler/src/elaborator/sig.rs +++ b/wado-compiler/src/elaborator/sig.rs @@ -3,7 +3,6 @@ use std::cell::RefCell; use std::rc::Rc; -use crate::ast::AstId; use crate::hashmap::IndexMap; use crate::module_source::ModuleSource; use crate::tir::{TypeId, TypeTable}; @@ -28,27 +27,28 @@ pub(crate) struct AssocConstSig { /// associated-const values, `__DATA__`. Assembled from `ModuleDecls` digests. #[derive(Default)] pub(crate) struct Signatures { - /// Canonical free-function signatures, declaring module → name. - pub(crate) function_sigs: IndexMap>>, + /// Canonical free-function signatures, keyed by the declaration. The + /// entries are shared with the per-module digests they are assembled from + /// rather than copied — a signature carries its parameter defaults' AST. + pub(crate) function_sigs: IndexMap>, - /// Canonical method signatures, keyed by the method's globally-unique - /// `AstId` — `impl`-block methods and `interface` / `resource` - /// operations alike. Dispatch goes index → signature, never AST. - pub(crate) method_sigs: IndexMap, + /// Canonical method signatures, keyed by the method's [`crate::defs::DefId`] + /// — `impl`-block methods and `interface` / `resource` operations alike. + /// Dispatch goes index → signature, never AST. + pub(crate) method_sigs: IndexMap, /// The name-keyed index over [`Self::method_sigs`] for `interface` / /// `resource` operations, which callers reach by name, not by node. - pub(crate) resource_method_ids: IndexMap<(AstId, String), AstId>, + pub(crate) resource_method_ids: IndexMap<(crate::defs::DefId, String), crate::defs::DefId>, /// Per-`impl`-block facts shared by the block's methods, keyed by the - /// block's `AstId`. - pub(crate) impl_sigs: IndexMap, + /// block's [`crate::defs::DefId`]. + pub(crate) impl_sigs: IndexMap, - /// Per-`trait`-declaration facts, keyed by the declaration's `AstId`. - /// `TraitEnv::decl_index` maps a canonical trait name to that `AstId`, - /// so a query reaches a trait's methods by name without loading the - /// declaring module's AST. - pub(crate) trait_sigs: IndexMap, + /// Per-`trait`-declaration facts, keyed by the declaration's + /// [`crate::defs::DefId`], so a query reaches a trait's methods without + /// loading the declaring module's AST. + pub(crate) trait_sigs: IndexMap, /// Global-variable declarations, declaring module → name → /// `(declared type, is_mut)`. @@ -65,31 +65,35 @@ pub(crate) struct Signatures { } impl Signatures { - /// Canonical signature of the free function `name` declared in `module`. - pub(crate) fn function_sig(&self, module: &ModuleSource, name: &str) -> Option<&FunctionSig> { - self.function_sigs.get(module)?.get(name) + /// Canonical signature of the free function `def` declares. + pub(crate) fn function_sig(&self, def: crate::defs::DefId) -> Option<&FunctionSig> { + self.function_sigs.get(&def).map(Rc::as_ref) } - /// Canonical signature of the method declared at `ast_id`. - pub(crate) fn method_sig(&self, ast_id: AstId) -> Option<&MethodSig> { - self.method_sigs.get(&ast_id) + /// Canonical signature of the method `def` declares. + pub(crate) fn method_sig(&self, def: crate::defs::DefId) -> Option<&MethodSig> { + self.method_sigs.get(&def) } /// Canonical signature of the operation `name` on the `interface` / - /// `resource` declared at `decl_id`. - pub(crate) fn resource_method_sig(&self, decl_id: AstId, name: &str) -> Option<&MethodSig> { - let method_id = self.resource_method_ids.get(&(decl_id, name.to_string()))?; - self.method_sig(*method_id) + /// `resource` declaration `decl`. + pub(crate) fn resource_method_sig( + &self, + decl: crate::defs::DefId, + name: &str, + ) -> Option<&MethodSig> { + let method = self.resource_method_ids.get(&(decl, name.to_string()))?; + self.method_sig(*method) } - /// Declaration facts of the `impl` block at `ast_id`. - pub(crate) fn impl_sig(&self, ast_id: AstId) -> Option<&ImplSig> { - self.impl_sigs.get(&ast_id) + /// Declaration facts of the `impl` block `def`. + pub(crate) fn impl_sig(&self, def: crate::defs::DefId) -> Option<&ImplSig> { + self.impl_sigs.get(&def) } - /// Declaration facts of the `trait` declared at `ast_id`. - pub(crate) fn trait_sig(&self, ast_id: AstId) -> Option<&TraitSig> { - self.trait_sigs.get(&ast_id) + /// Declaration facts of the `trait` `def` declares. + pub(crate) fn trait_sig(&self, def: crate::defs::DefId) -> Option<&TraitSig> { + self.trait_sigs.get(&def) } /// Declared type and mutability of the global `name` in `module`. @@ -134,10 +138,10 @@ pub(crate) struct DeclSig { /// declaration's — because dispatch asks them the same questions. #[derive(Clone, Debug)] pub(crate) struct MethodSig { - /// The declaring node — the key this signature is filed under, carried + /// The declaration — the key this signature is filed under, carried /// inside so a consumer holding the signature holds the identity too. /// A use→def edge is recorded from here, never from a name re-scan. - pub(crate) ast_id: AstId, + pub(crate) def: crate::defs::DefId, pub(crate) decl: DeclSig, pub(crate) self_kind: crate::ast::SelfKind, /// The non-receiver parameters, in order. `decl.param_types` includes @@ -153,7 +157,7 @@ pub(crate) struct MethodSig { /// The `impl` block that declares this method, where one does. How a caller /// reaches [`ImplSig::spelled_slots`], which aligns a spelled turbofish /// with the block's slots. - pub(crate) declaring_impl: Option, + pub(crate) declaring_impl: Option, /// The method's own slots as the declaration wrote them, parallel to /// [`Self::own_type_params`]. Bounds and defaults are irreducibly AST and /// live nowhere else, and a use site needs them to enforce the one and diff --git a/wado-compiler/src/elaborator/synth.rs b/wado-compiler/src/elaborator/synth.rs index 4815faf7d5..8a0ba8258e 100644 --- a/wado-compiler/src/elaborator/synth.rs +++ b/wado-compiler/src/elaborator/synth.rs @@ -626,7 +626,7 @@ impl Elaborator<'_, H> { { return self.class_of_type(sig.return_type); } - let Some(callee) = self.synth_callee_ref(&ident.name) else { + let Some(callee) = self.synth_callee_ref(ident) else { return ArgClass::Opaque(OpaqueReason::Inference); }; if !self.lookup_function_type_params(&callee).is_empty() { @@ -636,19 +636,14 @@ impl Elaborator<'_, H> { self.class_of_type(return_type) } - /// The callee identity of a plain `name(…)` call — a function the module - /// declares or imports. Anything else (a variant constructor, a static - /// path, an effect operation) is left to the expected type. - fn synth_callee_ref(&self, name: &str) -> Option { - if name.contains("::") { + /// The callee identity of a plain `name(…)` call, read off its own + /// reference site. A variant constructor, a static path or an effect + /// operation names no function there and is left to the expected type. + fn synth_callee_ref(&self, ident: &ast::IdentExpr) -> Option { + if ident.name.contains("::") { return None; } - if self.sem.decls.function_return_types.contains_key(name) { - return Some(CalleeRef::local(&self.current_module_source, name)); - } - let symbol = self.symbol_named(&self.current_module_source, name)?; - matches!(symbol.kind, crate::symbol::SymbolKind::Function(_)) - .then(|| CalleeRef::from_imported_symbol(symbol)) + Some(self.callee_of(self.free_function_at(ident.id)?)) } fn synth_method_call( diff --git a/wado-compiler/src/elaborator/trait_env.rs b/wado-compiler/src/elaborator/trait_env.rs index 76a0b80623..79013eb9b5 100644 --- a/wado-compiler/src/elaborator/trait_env.rs +++ b/wado-compiler/src/elaborator/trait_env.rs @@ -6,7 +6,7 @@ use std::sync::Arc; -use crate::ast::{self, AstId, Item, Module, Type}; +use crate::ast::{self, Item, Module, Type}; use crate::defs::DefId; use crate::hashmap::{IndexMap, IndexSet}; use crate::kiln::InvocationIndex; @@ -190,16 +190,16 @@ pub(crate) fn render_decl_name(defs: &crate::defs::DefTable, def: DefId) -> Stri /// Target type → the trait impl blocks written for it. Built once from all /// loaded modules so a method call costs a lookup rather than a scan. -pub(super) type TraitImplIndex = IndexMap>; +pub(super) type TraitImplIndex = IndexMap>; -type ReceiverImplIndex = IndexMap>; +type ReceiverImplIndex = IndexMap>; fn index_by_receiver(index: &TraitImplIndex, defs: &crate::defs::DefTable) -> ReceiverImplIndex { let mut out: ReceiverImplIndex = IndexMap::default(); for (key, entries) in index { out.entry(key.receiver(defs)) .or_default() - .extend(entries.iter().cloned()); + .extend(entries.iter().copied()); } out } @@ -207,7 +207,7 @@ fn index_by_receiver(index: &TraitImplIndex, defs: &crate::defs::DefTable) -> Re /// Digested header of an `impl` block, pre-extracted at [`TraitEnv::build`] /// time so trait/method queries read its trait name, target type, methods, /// and type parameters without re-fetching the impl block from -/// `loaded_modules`. Keyed by `(ModuleSource, AstId)` in +/// `loaded_modules`. Keyed by the block's [`DefId`] in /// [`TraitEnv::impl_headers`]. #[derive(Clone, Debug)] pub(super) struct ImplHeader { @@ -259,6 +259,13 @@ pub(super) struct ImplHeader { } impl ImplHeader { + /// Whether the block writes no type parameters. A concrete block hosts its + /// own function; a generic one's instance is materialised in the + /// receiver's module. + pub(super) fn is_concrete(&self) -> bool { + self.type_params.is_empty() + } + /// The implemented trait as a mangled method name embeds it: named by the /// module that declares it, carrying the header's written type arguments. /// `None` for an inherent impl, and for a trait position filled by a @@ -288,14 +295,13 @@ impl ImplHeader { /// type, so two modules' same-named bounds stay apart — the spelling the /// blanket wrote cannot answer that. fn blanket_pack_assocs( - impl_headers: &IndexMap<(ModuleSource, AstId), ImplHeader>, + impl_headers: &IndexMap, blanket_impls: &IndexMap>, resolutions: &crate::resolve::Resolutions, -) -> IndexMap<(ModuleSource, AstId), Vec<(DefId, String)>> { - let mut out: IndexMap<(ModuleSource, AstId), Vec<(DefId, String)>> = IndexMap::default(); +) -> IndexMap> { + let mut out: IndexMap> = IndexMap::default(); for blanket in blanket_impls.values().flatten() { - let key = (blanket.module.clone(), blanket.ast_id); - let Some(header) = impl_headers.get(&key) else { + let Some(header) = impl_headers.get(&blanket.def) else { continue; }; let pairs: Vec<(DefId, String)> = header @@ -314,7 +320,7 @@ fn blanket_pack_assocs( }) .collect(); if !pairs.is_empty() { - out.insert(key, pairs); + out.insert(blanket.def, pairs); } } out @@ -340,14 +346,13 @@ pub(crate) enum BlanketParamSource { /// is the point — type arguments are consumed positionally, so a receiver /// written after another parameter sits at a slot the caller never fills. fn blanket_param_sources( - impl_headers: &IndexMap<(ModuleSource, AstId), ImplHeader>, + impl_headers: &IndexMap, blanket_impls: &IndexMap>, resolutions: &crate::resolve::Resolutions, -) -> IndexMap<(ModuleSource, AstId), Vec> { - let mut out: IndexMap<(ModuleSource, AstId), Vec> = IndexMap::default(); +) -> IndexMap> { + let mut out: IndexMap> = IndexMap::default(); for blanket in blanket_impls.values().flatten() { - let key = (blanket.module.clone(), blanket.ast_id); - let Some(header) = impl_headers.get(&key) else { + let Some(header) = impl_headers.get(&blanket.def) else { continue; }; let sources: Vec = header @@ -377,7 +382,7 @@ fn blanket_param_sources( BlanketParamSource::Projection(def, assoc.name.clone()) }) .collect(); - out.insert(key, sources); + out.insert(blanket.def, sources); } out } @@ -388,9 +393,9 @@ fn blanket_param_sources( #[derive(Clone, Debug)] pub(super) struct ImplMethodHeader { pub(super) name: String, - /// The method's own `AstId` — the key into the canonical-signature + /// The method's own identity — the key into the canonical-signature /// digest, so a header lookup reaches the signature without the AST. - pub(super) ast_id: AstId, + pub(super) def: DefId, pub(super) type_params: Vec, /// Where the method is written, so a whole-program check reporting on it /// needs no second walk of the module AST to find the span. @@ -405,6 +410,30 @@ pub(super) struct ImplMethodHeader { pub(super) visibility: ast::Visibility, } +/// Digest each method a `trait` or `impl` block declares. One producer, so the +/// two cannot disagree about what a header says. +fn method_headers( + defs: &crate::defs::DefTable, + methods: &[ast::Function], +) -> Vec { + methods + .iter() + .map(|m| ImplMethodHeader { + name: m.name.clone(), + def: defs.def_at(m.id), + type_params: m.type_params.clone(), + span: m.span, + name_span: m.name_span, + param_count: m + .params + .iter() + .filter(|p| p.self_kind == ast::SelfKind::None) + .count(), + visibility: m.visibility, + }) + .collect() +} + /// The receiver shape of a blanket impl. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub(crate) enum BlanketReceiver { @@ -436,9 +465,9 @@ pub(crate) struct BlanketBound { #[derive(Clone, Debug)] pub(crate) struct BlanketImpl { pub(crate) module: ModuleSource, - /// The impl block's AST id; with `module`, the key into `impl_headers` for + /// The impl block's identity, and the key into `impl_headers` for /// consumers needing the full header (associated types, bound constraints). - pub(crate) ast_id: AstId, + pub(crate) def: DefId, pub(crate) receiver: BlanketReceiver, /// Receiver param name (`T` in `impl Trait for T`). pub(crate) param: String, @@ -483,7 +512,7 @@ fn classify_blanket_receiver( /// Digested header of a `trait` declaration: its name plus per-method /// signatures method-lookup queries read off the AST. Built in -/// [`TraitEnv::build`] and keyed by `(ModuleSource, AstId)` in +/// [`TraitEnv::build`] and keyed by the declaration's [`DefId`] in /// [`TraitEnv::trait_decl_headers`]. Reuses [`ImplMethodHeader`] for the /// per-method digest (name + type parameters). #[derive(Clone, Debug)] @@ -555,7 +584,7 @@ pub(super) struct StaticMethodEntry { /// The method itself: the key into the signature digest, which carries /// everything a lookup needs — resolved in the impl's own frame and its /// own module's perspective. - pub(super) method_id: AstId, + pub(super) method_id: DefId, } /// Pre-built index of static methods, for O(1) lookup instead of a scan over @@ -568,11 +597,12 @@ pub(super) struct StaticMethodEntry { pub(super) type StaticMethodIndex = IndexMap>; /// Pre-built index of static methods from resource declarations. -/// Key: canonical receiver [`DefId`] → `[(method_name, ModuleSource, -/// item_ast_id, method_index)]`. Same disambiguation rationale as +/// Key: canonical receiver [`DefId`] → `[(method_name, ModuleSource, owning +/// resource declaration, method_index)]` — the resource, not the method, unlike +/// [`StaticMethodEntry::method_id`]. Same disambiguation rationale as /// [`StaticMethodIndex`]. pub(super) type ResourceStaticMethodIndex = - IndexMap>; + IndexMap>; /// `(type_name, trait_name)` → modules holding that `impl` block. Keyed by bare /// names rather than [`DefId`]: the multi-value `Vec` plus the caller's @@ -667,7 +697,7 @@ fn push_module( /// a substituted call to a concrete impl's own module, while a generic impl's /// instance is materialised in the receiver type's. fn index_impl_modules( - impl_headers: &IndexMap<(ModuleSource, AstId), ImplHeader>, + impl_headers: &IndexMap, resolutions: &crate::resolve::Resolutions, concrete_only: bool, ) -> ImplModuleIndex { @@ -686,7 +716,7 @@ fn index_impl_modules( if matches!(header.target, ImplTargetKey::TypeParam(..)) { continue; } - if concrete_only && !header.type_params.is_empty() { + if concrete_only && !header.is_concrete() { continue; } let Some(fq_trait) = header.fq_trait(resolutions) else { @@ -737,21 +767,21 @@ pub struct TraitEnv { /// `effect_decl_index` to recognise handler-installable kinds in `with` /// clauses and `impl R for T` blocks. pub(super) resource_decl_index: ResourceDeclIndex, - /// Digested headers for every indexed impl block, keyed by - /// `(ModuleSource, AstId)`. Trait/method queries read this instead of - /// re-fetching the impl block AST from `loaded_modules`. See [`ImplHeader`]. - pub(super) impl_headers: IndexMap<(ModuleSource, AstId), ImplHeader>, + /// Digested headers for every indexed impl block, keyed by the block's + /// [`DefId`]. Trait/method queries read this instead of re-fetching the + /// impl block AST from `loaded_modules`. See [`ImplHeader`]. + pub(super) impl_headers: IndexMap, /// Per blanket impl, the `(declaring trait, associated type)` pairs whose /// binding is a type pack. Resolved once at build time from each bound's /// own reference site, so the trait is a declaration rather than the /// spelling the blanket wrote (WEP 2026-08-12). - pub(super) blanket_pack_assocs: IndexMap<(ModuleSource, AstId), Vec<(DefId, String)>>, + pub(super) blanket_pack_assocs: IndexMap>, /// Per blanket impl, what determines each of its parameters, in /// declaration order. Resolved once at build time from each bound's own /// reference site. - pub(super) blanket_param_sources: IndexMap<(ModuleSource, AstId), Vec>, - /// Digested headers for every `trait` declaration, keyed by - /// `(ModuleSource, AstId)`. Lets method-lookup queries read trait + pub(super) blanket_param_sources: IndexMap>, + /// Digested headers for every `trait` declaration, keyed by its + /// [`DefId`]. Lets method-lookup queries read trait /// method signatures without re-fetching the trait AST. See /// [`TraitDeclHeader`]. pub(super) trait_decl_headers: IndexMap, @@ -869,7 +899,7 @@ impl TraitEnv { &self, key: &ImplTargetKey, method_name: &str, - ) -> Option<&(String, ModuleSource, AstId, usize)> { + ) -> Option<&(String, ModuleSource, DefId, usize)> { self.resource_static_method_index .get(key)? .iter() @@ -904,7 +934,7 @@ impl TraitEnv { IndexMap::default(); let mut resource_decl_index: ResourceDeclIndex = IndexSet::default(); let mut blanket_impls: IndexMap> = IndexMap::default(); - let mut impl_headers: IndexMap<(ModuleSource, AstId), ImplHeader> = IndexMap::default(); + let mut impl_headers: IndexMap = IndexMap::default(); let mut trait_decl_headers: IndexMap = IndexMap::default(); let mut function_type_params: IndexMap<(ModuleSource, String), Vec> = IndexMap::default(); @@ -968,7 +998,7 @@ impl TraitEnv { .push(( method.name.clone(), module_source.clone(), - resource.id, + resource_key, method_idx, )); } @@ -1090,23 +1120,7 @@ impl TraitEnv { name: trait_decl.name.clone(), type_params: trait_decl.type_params.clone(), supertraits: trait_decl.supertraits.clone(), - methods: trait_decl - .methods - .iter() - .map(|m| ImplMethodHeader { - name: m.name.clone(), - ast_id: m.id, - type_params: m.type_params.clone(), - span: m.span, - name_span: m.name_span, - param_count: m - .params - .iter() - .filter(|p| p.self_kind == ast::SelfKind::None) - .count(), - visibility: m.visibility, - }) - .collect(), + methods: method_headers(defs, &trait_decl.methods), assoc_types: trait_decl.associated_types.clone(), span: trait_decl.span, }, @@ -1116,6 +1130,7 @@ impl TraitEnv { let Item::Impl(impl_block) = item else { continue; }; + let impl_def = defs.def_at(impl_block.id); let type_key = impl_target_key_at(&impl_block.ty, module_source, resolutions); let trait_ref: Option = impl_block .trait_type @@ -1132,7 +1147,7 @@ impl TraitEnv { ) }); impl_headers.insert( - (module_source.clone(), impl_block.id), + impl_def, ImplHeader { module: module_source.clone(), target: type_key.clone(), @@ -1142,23 +1157,7 @@ impl TraitEnv { trait_type: impl_block.trait_type.clone(), ty: impl_block.ty.clone(), type_params: impl_block.type_params.clone(), - methods: impl_block - .methods - .iter() - .map(|m| ImplMethodHeader { - name: m.name.clone(), - ast_id: m.id, - type_params: m.type_params.clone(), - span: m.span, - name_span: m.name_span, - param_count: m - .params - .iter() - .filter(|p| p.self_kind == ast::SelfKind::None) - .count(), - visibility: m.visibility, - }) - .collect(), + methods: method_headers(defs, &impl_block.methods), associated_types: impl_block.associated_types.clone(), is_synthesize_request: impl_block.is_synthesize_request, span: impl_block.span, @@ -1169,7 +1168,7 @@ impl TraitEnv { all_impl_index .entry(type_key.clone()) .or_default() - .push((module_source.clone(), impl_block.id)); + .push(impl_def); if impl_block.trait_type.is_some() { if let Some((receiver, param)) = classify_blanket_receiver(&impl_block.ty, &impl_block.type_params) @@ -1202,7 +1201,7 @@ impl TraitEnv { .or_default() .push(BlanketImpl { module: module_source.clone(), - ast_id: impl_block.id, + def: impl_def, receiver, param, bounds, @@ -1212,7 +1211,7 @@ impl TraitEnv { impl_index .entry(type_key.clone()) .or_default() - .push((module_source.clone(), impl_block.id)); + .push(impl_def); // Static methods on trait impl blocks (no `self` // parameter) join the same canonical bucket as // inherent statics. `f64::from_bits` and friends in @@ -1231,7 +1230,7 @@ impl TraitEnv { name: method.name.clone(), module: module_source.clone(), inherent_visibility: None, - method_id: method.id, + method_id: defs.def_at(method.id), }); } } @@ -1252,7 +1251,7 @@ impl TraitEnv { name: method.name.clone(), module: module_source.clone(), inherent_visibility: Some(method.visibility), - method_id: method.id, + method_id: defs.def_at(method.id), }); } } @@ -1380,7 +1379,7 @@ impl TraitEnv { /// Keys of every impl block on `type_key`, in global build order — /// inherent and trait alike. - pub(super) fn all_impl_keys(&self, type_key: &ImplTargetKey) -> Vec<(ModuleSource, AstId)> { + pub(super) fn all_impl_keys(&self, type_key: &ImplTargetKey) -> Vec { self.all_impl_index .get(type_key) .cloned() @@ -1390,10 +1389,7 @@ impl TraitEnv { /// Keys of the **inherent** impls on `type_name`, in global build order — /// the `trait_name.is_none()` subset of [`Self::all_impl_index`]. Used by /// instance-method lookup, which must not treat trait impls as inherent. - pub(super) fn inherent_impl_keys( - &self, - type_key: &ImplTargetKey, - ) -> Vec<(ModuleSource, AstId)> { + pub(super) fn inherent_impl_keys(&self, type_key: &ImplTargetKey) -> Vec { self.all_impl_index .get(type_key) .map(|keys| { @@ -1403,7 +1399,7 @@ impl TraitEnv { .get(*key) .is_some_and(|h| h.trait_name.is_none()) }) - .cloned() + .copied() .collect() }) .unwrap_or_default() @@ -1492,20 +1488,17 @@ impl TraitEnv { pub(crate) fn entries_by_receiver<'a>( &'a self, receiver: &'a name::Receiver, - ) -> impl Iterator + 'a { + ) -> impl Iterator + 'a { self.by_receiver .get(receiver) .into_iter() - .flat_map(|entries| entries.iter()) + .flat_map(|entries| entries.iter().copied()) } /// Collected form of [`Self::entries_by_receiver`], for callers that need /// to iterate the widened match more than once. - pub(crate) fn entries_by_receiver_vec( - &self, - receiver: &name::Receiver, - ) -> Vec<(ModuleSource, AstId)> { - self.entries_by_receiver(receiver).cloned().collect() + pub(crate) fn entries_by_receiver_vec(&self, receiver: &name::Receiver) -> Vec { + self.entries_by_receiver(receiver).collect() } /// Receiver-matched form of [`Self::has_any_methodful_impl`]. @@ -1538,8 +1531,9 @@ impl TraitEnv { trait_: crate::defs::DefId, module_source: &ModuleSource, ) -> bool { - self.entries_by_receiver(receiver) - .any(|entry| entry.0 == *module_source && self.methodful_header_matches(entry, trait_)) + self.entries_by_receiver(receiver).any(|entry| { + self.defs.module(entry) == module_source && self.methodful_header_matches(entry, trait_) + }) } /// Whether an inherent `impl` on `receiver` declares `method_name`. @@ -1559,13 +1553,9 @@ impl TraitEnv { }) } - fn methodful_header_matches( - &self, - entry: &(ModuleSource, AstId), - trait_: crate::defs::DefId, - ) -> bool { + fn methodful_header_matches(&self, entry: DefId, trait_: crate::defs::DefId) -> bool { self.impl_headers - .get(entry) + .get(&entry) .is_some_and(|header| !header.methods.is_empty() && header.trait_ref == Some(trait_)) } @@ -1646,7 +1636,7 @@ impl TraitEnv { /// order — see [`blanket_param_sources`]. pub(crate) fn blanket_param_sources(&self, blanket: &BlanketImpl) -> Vec { self.blanket_param_sources - .get(&(blanket.module.clone(), blanket.ast_id)) + .get(&blanket.def) .cloned() .unwrap_or_default() } @@ -1659,7 +1649,7 @@ impl TraitEnv { /// `Members`. pub(crate) fn pack_assocs_of_blanket(&self, blanket: &BlanketImpl) -> Vec<(DefId, String)> { self.blanket_pack_assocs - .get(&(blanket.module.clone(), blanket.ast_id)) + .get(&blanket.def) .cloned() .unwrap_or_default() } @@ -2229,7 +2219,7 @@ impl VariadicImpl<'_> { /// their own. The same walk refuses a target the compiler cannot implement. fn check_variadic_impl_overlap( defs: &crate::defs::DefTable, - impl_headers: &IndexMap<(ModuleSource, AstId), ImplHeader>, + impl_headers: &IndexMap, ) -> Vec<(ModuleSource, TypeError)> { let mut violations = Vec::new(); let mut groups: IndexMap<&ImplTargetKey, Vec>> = IndexMap::default(); @@ -2335,7 +2325,7 @@ fn target_mentions_impl_param(ty: &ast::Type, params: &IndexSet<&str>) -> bool { /// written head — two modules' `Box_` are two types, and a spelling cannot say so. fn check_inherent_impl_collisions( defs: &crate::defs::DefTable, - impl_headers: &IndexMap<(ModuleSource, AstId), ImplHeader>, + impl_headers: &IndexMap, resolutions: &crate::resolve::Resolutions, ) -> Vec<(ModuleSource, TypeError)> { let mut generic_methods_by_target: IndexMap<&ImplTargetKey, IndexSet<&str>> = @@ -2412,7 +2402,7 @@ fn check_inherent_impl_collisions( /// paired with the offending impl's [`ModuleSource`] for file attribution. fn check_all_orphan_rules( defs: &crate::defs::DefTable, - impl_headers: &IndexMap<(ModuleSource, AstId), ImplHeader>, + impl_headers: &IndexMap, decl_index: &TraitDeclIndex, type_decl_index: &IndexSet, resolve: ResolveWritten<'_>, diff --git a/wado-compiler/src/elaborator/trait_query.rs b/wado-compiler/src/elaborator/trait_query.rs index d85038af5f..785e324897 100644 --- a/wado-compiler/src/elaborator/trait_query.rs +++ b/wado-compiler/src/elaborator/trait_query.rs @@ -204,14 +204,13 @@ fn mentions_self(ty: &ast::Type) -> bool { /// AST, and answers `None` for a declaration that is no trait. pub(crate) fn trait_sig_of_with<'a>( decl: crate::defs::DefId, - resolutions: &crate::resolve::Resolutions, trait_env: &super::trait_env::TraitEnv, signatures: &'a super::sig::Signatures, ) -> Option<&'a super::sig::TraitSig> { if !trait_env.decl_index.contains(&decl) { return None; } - signatures.trait_sig(resolutions.defs().ast_id(decl)) + signatures.trait_sig(decl) } /// The structural-conformance rule's answer for one type: whether every member @@ -371,12 +370,7 @@ impl Elaborator<'_, H> { /// [`Self::trait_sig_of`] for a caller still holding a trait's spelling. pub(super) fn trait_sig_by_name(&self, trait_name: &str) -> Option<&TraitSig> { let decl = self.decl_key_or_local(trait_name)?; - trait_sig_of_with( - decl, - &self.tysys.resolutions, - &self.tysys.trait_env, - &self.tysys.signatures, - ) + trait_sig_of_with(decl, &self.tysys.trait_env, &self.tysys.signatures) } /// The declaration header of the trait `trait_name` names in this frame. @@ -1067,14 +1061,12 @@ impl TypeSystem { // A receiverless method has no receiver to deref, so `&T` inherits it // by forwarding — which works only where `Self` is absent from the // signature: `kind() -> String` forwards, `-> Option` cannot. - trait_sig_of_with(trait_, &self.resolutions, &self.trait_env, &self.signatures).is_some_and( - |sig| { - sig.methods.values().any(|m| { - m.sig.self_kind == crate::ast::SelfKind::None - && self.receiverless_method_mentions_self(&m.sig) - }) - }, - ) + trait_sig_of_with(trait_, &self.trait_env, &self.signatures).is_some_and(|sig| { + sig.methods.values().any(|m| { + m.sig.self_kind == crate::ast::SelfKind::None + && self.receiverless_method_mentions_self(&m.sig) + }) + }) } /// Whether a receiverless method's signature names `Self` — in a parameter, @@ -1751,9 +1743,7 @@ impl Elaborator<'_, H> { if !self.tysys.trait_env.decl_index.contains(key) { return None; } - self.tysys - .signatures - .trait_sig(self.tysys.resolutions.defs().ast_id(*key)) + self.tysys.signatures.trait_sig(*key) } /// What `Self::X` means for a receiver reached through a trait bound, for @@ -1934,7 +1924,7 @@ impl Elaborator<'_, H> { Some(( fq_trait_name, MethodInfo { - method_ast_id: Some(sig.ast_id), + method_def: Some(sig.def), return_type: instantiated.return_type, self_kind: sig.self_kind, param_types: instantiated.param_types[first_value_param..].to_vec(), @@ -2443,10 +2433,7 @@ impl Elaborator<'_, H> { let blanket_infos: Vec = { let mut result = vec![]; for blanket in trait_env.blanket_impls.get(&trait_).into_iter().flatten() { - let Some(header) = trait_env - .impl_headers - .get(&(blanket.module.clone(), blanket.ast_id)) - else { + let Some(header) = trait_env.impl_headers.get(&blanket.def) else { continue; }; if header.associated_types.is_empty() { @@ -2554,12 +2541,18 @@ impl Elaborator<'_, H> { // `output_type` to the receiver type absent a `type Output`. The set and // the types come from `TypeSystem::auto_derive_by_trait`. let auto_derive = self.tysys.auto_derive_by_trait(trait_name); - let (info_trait_name, self_kind, param_types, return_type) = if let Some(info) = + let (info_trait_name, self_kind, param_types, return_type, impl_def) = if let Some(info) = self.find_arithmetic_trait_impl(struct_name, lookup_type_id, trait_, method_name, None) { let return_type = auto_derive.map_or(info.output_type, |(_, ty)| ty); let param_types = info.rhs_type.map(|t| vec![t]).unwrap_or_default(); - (info.trait_name, info.self_kind, param_types, return_type) + ( + info.trait_name, + info.self_kind, + param_types, + return_type, + Some(info.impl_def), + ) } else if let Some((item, return_type)) = auto_derive && let Some(trait_) = self.tysys.compiler_trait_def(item) && self.tysys.type_implements_trait( @@ -2574,11 +2567,13 @@ impl Elaborator<'_, H> { .type_table .borrow_mut() .intern(ResolvedType::Ref(lookup_type_id)); + // Auto-derived: no `impl` block is written, so none is named. ( self.tysys.type_table.borrow().compiler_trait_fq(item), ast::SelfKind::Ref, vec![ref_self_ty], return_type, + None, ) } else { return None; @@ -2586,6 +2581,7 @@ impl Elaborator<'_, H> { Some(ResolvedTraitMethod { trait_name: info_trait_name, method_name: method_name.to_string(), + impl_def, impl_name: struct_name.to_string(), impl_type_id: (!is_type_param).then_some(lookup_type_id), self_kind, @@ -2626,7 +2622,7 @@ impl Elaborator<'_, H> { .borrow_mut() .intern(ResolvedType::Ref(base_type_id)); let method_info = MethodInfo { - method_ast_id: None, + method_def: None, return_type, self_kind: ast::SelfKind::Ref, param_types: vec![ref_self_ty], diff --git a/wado-compiler/src/elaborator/types.rs b/wado-compiler/src/elaborator/types.rs index 8507ebd115..b5d5a8197c 100644 --- a/wado-compiler/src/elaborator/types.rs +++ b/wado-compiler/src/elaborator/types.rs @@ -1801,12 +1801,11 @@ impl MethodOwner { #[derive(Debug, Clone)] pub(super) struct MethodInfo { - /// The declaring node of the method this lookup selected, taken from its - /// [`crate::elaborator::sig::MethodSig`]. The use→def edge for a call is - /// recorded from here, so it names the impl dispatch actually chose. - /// `None` where no declaration backs the signature: the tuple builtins, - /// an auto-derived `Eq` / `Ord`, and the error-recovery placeholder. - pub(super) method_ast_id: Option, + /// The method this lookup selected. A call's use→def edge is recorded from + /// here, so it names the impl dispatch chose. `None` where no declaration + /// backs the signature: tuple builtins, auto-derived `Eq` / `Ord`, the + /// error-recovery placeholder. + pub(super) method_def: Option, pub(super) return_type: TypeId, pub(super) self_kind: ast::SelfKind, /// Parameter types (excluding self) @@ -2587,6 +2586,9 @@ pub(super) struct IndexValueTraitInfo { /// Info about an operator trait implementation #[derive(Clone)] pub(super) struct ArithmeticTraitInfo { + /// The `impl` block that matched. The module a dispatch is recorded + /// against is read off it, so no rendering is compared to find one. + pub(super) impl_def: crate::defs::DefId, /// The Output associated type pub(super) output_type: TypeId, /// Self kind for the method (&self) @@ -2611,6 +2613,10 @@ pub(super) struct ResolvedTraitMethod { pub(super) trait_name: crate::name::FqTraitName, /// Method name (e.g., "eq", "cmp", "add", "shl", "neg", "bitnot"). pub(super) method_name: String, + /// The `impl` block dispatch matched. `None` where none is named: an + /// auto-derived `Eq` / `Ord`, and a method reached through a type + /// parameter's bound, whose block monomorphization picks. + pub(super) impl_def: Option, /// Written name of the type whose impl matched — the impl-index key. For /// newtypes this may be the ultimate base-type name when dispatch falls /// back to the base impl. diff --git a/wado-compiler/src/resolve.rs b/wado-compiler/src/resolve.rs index 7489fb6e12..65fbdab953 100644 --- a/wado-compiler/src/resolve.rs +++ b/wado-compiler/src/resolve.rs @@ -471,7 +471,9 @@ impl AstVisitor for Resolver<'_> { fn visit_block(&mut self, block: &ast::Block) { let mut scope = IndexMap::default(); for stmt in &block.stmts { + // A local `impl` block writes no name for the scope to hold. if let ast::Stmt::Item(item) = stmt + && !matches!(**item, ast::Item::Impl(_)) && let Some(def) = self.defs.of_ast_id(item.id()) { scope.insert(self.defs.name(def).to_string(), def); diff --git a/wado-compiler/src/tir.rs b/wado-compiler/src/tir.rs index aa8a012923..5c89901229 100644 --- a/wado-compiler/src/tir.rs +++ b/wado-compiler/src/tir.rs @@ -1797,10 +1797,7 @@ impl TypeTable { .compiler_items .struct_decl(item) .expect("a registered struct item records its declaring node"); - let def = self - .defs - .of_ast_id(decl) - .expect("a compiler item's declaring node is a declaration"); + let def = self.defs.def_at(decl); crate::name::FqTypeName::declared(&self.defs, def) } diff --git a/wado-compiler/tests/fixtures/aliased_impl_head.wado b/wado-compiler/tests/fixtures/aliased_impl_head.wado new file mode 100644 index 0000000000..9e95139fad --- /dev/null +++ b/wado-compiler/tests/fixtures/aliased_impl_head.wado @@ -0,0 +1,19 @@ +// An impl block writes its target under the alias its own module imported, so +// the header's head is neither the declaration's name nor a call site's. + +use { Far as Near } from "./sub/aliased_impl_head_a.wado"; + +struct Local { + v: i32, +} + +impl From for Near { + fn from(value: Local) -> Near { + return Near { v: value.v * 10 }; + } +} + +test "a conversion impl written under an alias answers its target" { + let n = Near::from(Local { v: 3 }); + assert n.v == 30; +} diff --git a/wado-compiler/tests/fixtures/cross_module_same_name_conversion.wado b/wado-compiler/tests/fixtures/cross_module_same_name_conversion.wado new file mode 100644 index 0000000000..65e5ef9560 --- /dev/null +++ b/wado-compiler/tests/fixtures/cross_module_same_name_conversion.wado @@ -0,0 +1,22 @@ +// An import alias beside a same-named local type: the receiver's declaration +// picks the impl, and neither module's spelling of it can. + +use { Meters as M } from "./sub/cross_module_same_name_conversion_a.wado"; + +struct Meters { + v: i32, +} + +impl From for Meters { + fn from(value: i32) -> Meters { + return Meters { v: value + 1000 }; + } +} + +test "a conversion keys to the receiver's declaration, not the caller's same-named type" { + let imported = M::from(3); + assert imported.v == 30; + + let local = Meters::from(3); + assert local.v == 1003; +} diff --git a/wado-compiler/tests/fixtures/cross_module_same_name_default_fn.wado b/wado-compiler/tests/fixtures/cross_module_same_name_default_fn.wado new file mode 100644 index 0000000000..81a6af4fa9 --- /dev/null +++ b/wado-compiler/tests/fixtures/cross_module_same_name_default_fn.wado @@ -0,0 +1,13 @@ +// The caller declares a `tag` of its own, so a default resolved from the +// walking module's scope reaches the wrong one. + +use { labelled } from "./sub/cross_module_same_name_default_fn_a.wado"; + +fn tag() -> i32 { + return 2; +} + +test "a parameter default names the callee module's function" { + assert labelled() == 1; + assert tag() == 2; +} diff --git a/wado-compiler/tests/fixtures/cross_module_same_name_gassoc.wado b/wado-compiler/tests/fixtures/cross_module_same_name_gassoc.wado new file mode 100644 index 0000000000..d64e48108c --- /dev/null +++ b/wado-compiler/tests/fixtures/cross_module_same_name_gassoc.wado @@ -0,0 +1,16 @@ +// A's `Node` binds `Out` to slot 0 from an impl written outside A; B's +// `Node` binds it to slot 1 from an impl in its own module. A +// build-order pick between the two spellings swaps the answers. + +use { Unwrap } from "./sub/cross_module_same_name_gassoc_trait.wado"; +use { Node as NodeB } from "./sub/cross_module_same_name_gassoc_b.wado"; +use { Node as NodeA } from "./sub/cross_module_same_name_gassoc_a.wado"; +use {} from "./sub/cross_module_same_name_gassoc_impl.wado"; + +test "a generic impl's associated type belongs to the declaration its header names" { + let a = NodeA { v: 7 }; + assert a.unwrap_it() == 7; + + let b = NodeB { k: "key", v: 9 }; + assert b.unwrap_it() == 9; +} diff --git a/wado-compiler/tests/fixtures/newtype_base_conversion_keys_base_module.wado b/wado-compiler/tests/fixtures/newtype_base_conversion_keys_base_module.wado new file mode 100644 index 0000000000..92e9b0eb15 --- /dev/null +++ b/wado-compiler/tests/fixtures/newtype_base_conversion_keys_base_module.wado @@ -0,0 +1,26 @@ +// A newtype's static call dispatches to its base, whose declaration is the +// imported module's. The caller's same-named `Meters` must not answer for it. +// +// A base's *trait* static is not inherited yet, so the call still fails; what +// this pins is which module's impls the report reads — `i64` is the base's, +// `String` the caller's. + +use { Km } from "./sub/newtype_base_conversion_module_a.wado"; + +struct Meters { + v: i64, +} + +impl From for Meters { + fn from(value: String) -> Meters { + return Meters { v: 1000 }; + } +} + +test "a newtype's conversion report names its base's impls" { + let k = Km::from(5); + assert k.v == 50; +} + +__DATA__ +{"test": {}, "compile_error": "'Meters::from' is available for 'i64'"} diff --git a/wado-compiler/tests/fixtures/shadow_prelude_panic.wado b/wado-compiler/tests/fixtures/shadow_prelude_panic.wado new file mode 100644 index 0000000000..6c3d0c8b11 --- /dev/null +++ b/wado-compiler/tests/fixtures/shadow_prelude_panic.wado @@ -0,0 +1,17 @@ +// Both names are the prelude's, and a module's own declaration outranks it. + +fn panic(msg: String) -> i32 { + return 7; +} + +fn unreachable() -> i32 { + return 9; +} + +test "a module's own panic outranks the prelude's" { + assert panic("x") == 7; +} + +test "a module's own unreachable outranks the prelude's" { + assert unreachable() == 9; +} diff --git a/wado-compiler/tests/fixtures/sub/aliased_impl_head_a.wado b/wado-compiler/tests/fixtures/sub/aliased_impl_head_a.wado new file mode 100644 index 0000000000..30d070e1c8 --- /dev/null +++ b/wado-compiler/tests/fixtures/sub/aliased_impl_head_a.wado @@ -0,0 +1,5 @@ +// The receiver, declared under a name the impl block never writes. + +pub struct Far { + pub v: i32, +} diff --git a/wado-compiler/tests/fixtures/sub/cross_module_same_name_conversion_a.wado b/wado-compiler/tests/fixtures/sub/cross_module_same_name_conversion_a.wado new file mode 100644 index 0000000000..01545a201b --- /dev/null +++ b/wado-compiler/tests/fixtures/sub/cross_module_same_name_conversion_a.wado @@ -0,0 +1,11 @@ +// The receiver the call actually names, declared outside the caller. + +pub struct Meters { + pub v: i32, +} + +impl From for Meters { + fn from(value: i32) -> Meters { + return Meters { v: value * 10 }; + } +} diff --git a/wado-compiler/tests/fixtures/sub/cross_module_same_name_default_fn_a.wado b/wado-compiler/tests/fixtures/sub/cross_module_same_name_default_fn_a.wado new file mode 100644 index 0000000000..064dd964d9 --- /dev/null +++ b/wado-compiler/tests/fixtures/sub/cross_module_same_name_default_fn_a.wado @@ -0,0 +1,9 @@ +// A parameter default calling this module's own private `tag`. + +fn tag() -> i32 { + return 1; +} + +pub fn labelled(v: i32 = tag()) -> i32 { + return v; +} diff --git a/wado-compiler/tests/fixtures/sub/cross_module_same_name_gassoc_a.wado b/wado-compiler/tests/fixtures/sub/cross_module_same_name_gassoc_a.wado new file mode 100644 index 0000000000..845530b056 --- /dev/null +++ b/wado-compiler/tests/fixtures/sub/cross_module_same_name_gassoc_a.wado @@ -0,0 +1,4 @@ +// Module A's `Node`, whose generic impl is written elsewhere. +pub struct Node { + pub v: T, +} diff --git a/wado-compiler/tests/fixtures/sub/cross_module_same_name_gassoc_b.wado b/wado-compiler/tests/fixtures/sub/cross_module_same_name_gassoc_b.wado new file mode 100644 index 0000000000..34c1074d87 --- /dev/null +++ b/wado-compiler/tests/fixtures/sub/cross_module_same_name_gassoc_b.wado @@ -0,0 +1,14 @@ +// The same spelling as A's `Node`, implemented in its own module. +use { Unwrap } from "./cross_module_same_name_gassoc_trait.wado"; + +pub struct Node { + pub k: K, + pub v: V, +} + +impl Unwrap for Node { + type Out = V; + fn unwrap_it(&self) -> Self::Out { + return self.v; + } +} diff --git a/wado-compiler/tests/fixtures/sub/cross_module_same_name_gassoc_impl.wado b/wado-compiler/tests/fixtures/sub/cross_module_same_name_gassoc_impl.wado new file mode 100644 index 0000000000..406e97f4b8 --- /dev/null +++ b/wado-compiler/tests/fixtures/sub/cross_module_same_name_gassoc_impl.wado @@ -0,0 +1,10 @@ +// The impl for A's `Node`, written outside A. +use { Unwrap } from "./cross_module_same_name_gassoc_trait.wado"; +use { Node } from "./cross_module_same_name_gassoc_a.wado"; + +impl Unwrap for Node { + type Out = T; + fn unwrap_it(&self) -> Self::Out { + return self.v; + } +} diff --git a/wado-compiler/tests/fixtures/sub/cross_module_same_name_gassoc_trait.wado b/wado-compiler/tests/fixtures/sub/cross_module_same_name_gassoc_trait.wado new file mode 100644 index 0000000000..499e4a86b5 --- /dev/null +++ b/wado-compiler/tests/fixtures/sub/cross_module_same_name_gassoc_trait.wado @@ -0,0 +1,5 @@ +// The trait both same-named `Node`s implement. +pub trait Unwrap { + type Out; + fn unwrap_it(&self) -> Self::Out; +} diff --git a/wado-compiler/tests/fixtures/sub/newtype_base_conversion_module_a.wado b/wado-compiler/tests/fixtures/sub/newtype_base_conversion_module_a.wado new file mode 100644 index 0000000000..60d102f0ab --- /dev/null +++ b/wado-compiler/tests/fixtures/sub/newtype_base_conversion_module_a.wado @@ -0,0 +1,13 @@ +// The base the newtype dispatches to, and its own conversion. + +pub struct Meters { + pub v: i64, +} + +impl From for Meters { + fn from(value: i64) -> Meters { + return Meters { v: value * 10 }; + } +} + +pub type Km = Meters; diff --git a/wado-compiler/tests/generated/fixtures/aliased_impl_head.wir.wado b/wado-compiler/tests/generated/fixtures/aliased_impl_head.wir.wado new file mode 100644 index 0000000000..a69d30fc0a --- /dev/null +++ b/wado-compiler/tests/generated/fixtures/aliased_impl_head.wir.wado @@ -0,0 +1,32 @@ +// Golden file: WIR with -O2 optimization +// Source: wado-compiler/tests/fixtures/aliased_impl_head.wado +// Generated by: mise run update-golden-fixtures + +type "functype//mem/realloc" = fn(i32, i32, i32, i32) -> i32; // TypeId(0) + +type "functype//wasi/task-return" = fn(i32); // TypeId(1) + +type "functype//aliased_impl_head.wado/__cm_export____test_0_a_conversion_impl_written_under_an_alias_answers_its_target" = fn(); // TypeId(2) + +type "functype//core:prelude/fpfmt.wado/__initialize_module" = fn(); // TypeId(3) + +import fn mem/realloc from "mem/realloc"; +import fn wasi/task-return from "wasi/task-return"; +import memory (1) from "mem/memory"; + +global mut global:aliased_impl_head.wado::__modules_initialized: bool = 0; + +fn "aliased_impl_head.wado/__cm_export____test_0_a_conversion_impl_written_under_an_alias_answers_its_target"() { + __inline___initialize_modules_0: block { + break_if __inline___initialize_modules_0 @likely(global:aliased_impl_head.wado::__modules_initialized); + cold_path; + "core:prelude/fpfmt.wado/__initialize_module"(); + global:aliased_impl_head.wado::__modules_initialized = 1; + }; + "wasi/task-return"(0); +} + +fn "core:prelude/fpfmt.wado/__initialize_module"() { +} + +export fn "aliased_impl_head.wado/__cm_export____test_0_a_conversion_impl_written_under_an_alias_answers_its_target" as "__test_0_a_conversion_impl_written_under_an_alias_answers_its_target" diff --git a/wado-compiler/tests/generated/fixtures/cross_module_same_name_conversion.wir.wado b/wado-compiler/tests/generated/fixtures/cross_module_same_name_conversion.wir.wado new file mode 100644 index 0000000000..768e205348 --- /dev/null +++ b/wado-compiler/tests/generated/fixtures/cross_module_same_name_conversion.wir.wado @@ -0,0 +1,32 @@ +// Golden file: WIR with -O2 optimization +// Source: wado-compiler/tests/fixtures/cross_module_same_name_conversion.wado +// Generated by: mise run update-golden-fixtures + +type "functype//mem/realloc" = fn(i32, i32, i32, i32) -> i32; // TypeId(0) + +type "functype//wasi/task-return" = fn(i32); // TypeId(1) + +type "functype//cross_module_same_name_conversion.wado/__cm_export____test_0_a_conversion_keys_to_the_receiver_s_declaration__not_the_caller_s_same_named_type" = fn(); // TypeId(2) + +type "functype//core:prelude/fpfmt.wado/__initialize_module" = fn(); // TypeId(3) + +import fn mem/realloc from "mem/realloc"; +import fn wasi/task-return from "wasi/task-return"; +import memory (1) from "mem/memory"; + +global mut global:cross_module_same_name_conversion.wado::__modules_initialized: bool = 0; + +fn "cross_module_same_name_conversion.wado/__cm_export____test_0_a_conversion_keys_to_the_receiver_s_declaration__not_the_caller_s_same_named_type"() { + __inline___initialize_modules_0: block { + break_if __inline___initialize_modules_0 @likely(global:cross_module_same_name_conversion.wado::__modules_initialized); + cold_path; + "core:prelude/fpfmt.wado/__initialize_module"(); + global:cross_module_same_name_conversion.wado::__modules_initialized = 1; + }; + "wasi/task-return"(0); +} + +fn "core:prelude/fpfmt.wado/__initialize_module"() { +} + +export fn "cross_module_same_name_conversion.wado/__cm_export____test_0_a_conversion_keys_to_the_receiver_s_declaration__not_the_caller_s_same_named_type" as "__test_0_a_conversion_keys_to_the_receiver_s_declaration__not_the_caller_s_same_named_type" diff --git a/wado-compiler/tests/generated/fixtures/cross_module_same_name_default_fn.wir.wado b/wado-compiler/tests/generated/fixtures/cross_module_same_name_default_fn.wir.wado new file mode 100644 index 0000000000..050e399b15 --- /dev/null +++ b/wado-compiler/tests/generated/fixtures/cross_module_same_name_default_fn.wir.wado @@ -0,0 +1,32 @@ +// Golden file: WIR with -O2 optimization +// Source: wado-compiler/tests/fixtures/cross_module_same_name_default_fn.wado +// Generated by: mise run update-golden-fixtures + +type "functype//mem/realloc" = fn(i32, i32, i32, i32) -> i32; // TypeId(0) + +type "functype//wasi/task-return" = fn(i32); // TypeId(1) + +type "functype//cross_module_same_name_default_fn.wado/__cm_export____test_0_a_parameter_default_names_the_callee_module_s_function" = fn(); // TypeId(2) + +type "functype//core:prelude/fpfmt.wado/__initialize_module" = fn(); // TypeId(3) + +import fn mem/realloc from "mem/realloc"; +import fn wasi/task-return from "wasi/task-return"; +import memory (1) from "mem/memory"; + +global mut global:cross_module_same_name_default_fn.wado::__modules_initialized: bool = 0; + +fn "cross_module_same_name_default_fn.wado/__cm_export____test_0_a_parameter_default_names_the_callee_module_s_function"() { + __inline___initialize_modules_0: block { + break_if __inline___initialize_modules_0 @likely(global:cross_module_same_name_default_fn.wado::__modules_initialized); + cold_path; + "core:prelude/fpfmt.wado/__initialize_module"(); + global:cross_module_same_name_default_fn.wado::__modules_initialized = 1; + }; + "wasi/task-return"(0); +} + +fn "core:prelude/fpfmt.wado/__initialize_module"() { +} + +export fn "cross_module_same_name_default_fn.wado/__cm_export____test_0_a_parameter_default_names_the_callee_module_s_function" as "__test_0_a_parameter_default_names_the_callee_module_s_function" diff --git a/wado-compiler/tests/generated/fixtures/cross_module_same_name_gassoc.wir.wado b/wado-compiler/tests/generated/fixtures/cross_module_same_name_gassoc.wir.wado new file mode 100644 index 0000000000..f2a27de9a5 --- /dev/null +++ b/wado-compiler/tests/generated/fixtures/cross_module_same_name_gassoc.wir.wado @@ -0,0 +1,32 @@ +// Golden file: WIR with -O2 optimization +// Source: wado-compiler/tests/fixtures/cross_module_same_name_gassoc.wado +// Generated by: mise run update-golden-fixtures + +type "functype//mem/realloc" = fn(i32, i32, i32, i32) -> i32; // TypeId(0) + +type "functype//wasi/task-return" = fn(i32); // TypeId(1) + +type "functype//cross_module_same_name_gassoc.wado/__cm_export____test_0_a_generic_impl_s_associated_type_belongs_to_the_declaration_its_header_names" = fn(); // TypeId(2) + +type "functype//core:prelude/fpfmt.wado/__initialize_module" = fn(); // TypeId(3) + +import fn mem/realloc from "mem/realloc"; +import fn wasi/task-return from "wasi/task-return"; +import memory (1) from "mem/memory"; + +global mut global:cross_module_same_name_gassoc.wado::__modules_initialized: bool = 0; + +fn "cross_module_same_name_gassoc.wado/__cm_export____test_0_a_generic_impl_s_associated_type_belongs_to_the_declaration_its_header_names"() { + __inline___initialize_modules_0: block { + break_if __inline___initialize_modules_0 @likely(global:cross_module_same_name_gassoc.wado::__modules_initialized); + cold_path; + "core:prelude/fpfmt.wado/__initialize_module"(); + global:cross_module_same_name_gassoc.wado::__modules_initialized = 1; + }; + "wasi/task-return"(0); +} + +fn "core:prelude/fpfmt.wado/__initialize_module"() { +} + +export fn "cross_module_same_name_gassoc.wado/__cm_export____test_0_a_generic_impl_s_associated_type_belongs_to_the_declaration_its_header_names" as "__test_0_a_generic_impl_s_associated_type_belongs_to_the_declaration_its_header_names" diff --git a/wado-compiler/tests/generated/fixtures/shadow_prelude_panic.wir.wado b/wado-compiler/tests/generated/fixtures/shadow_prelude_panic.wir.wado new file mode 100644 index 0000000000..dca1566a03 --- /dev/null +++ b/wado-compiler/tests/generated/fixtures/shadow_prelude_panic.wir.wado @@ -0,0 +1,45 @@ +// Golden file: WIR with -O2 optimization +// Source: wado-compiler/tests/fixtures/shadow_prelude_panic.wado +// Generated by: mise run update-golden-fixtures + +type "functype//mem/realloc" = fn(i32, i32, i32, i32) -> i32; // TypeId(0) + +type "functype//wasi/task-return" = fn(i32); // TypeId(1) + +type "functype//shadow_prelude_panic.wado/__cm_export____test_0_a_module_s_own_panic_outranks_the_prelude_s" = fn(); // TypeId(2) + +type "functype//shadow_prelude_panic.wado/__cm_export____test_1_a_module_s_own_unreachable_outranks_the_prelude_s" = fn(); // TypeId(3) + +type "functype//core:prelude/fpfmt.wado/__initialize_module" = fn(); // TypeId(4) + +import fn mem/realloc from "mem/realloc"; +import fn wasi/task-return from "wasi/task-return"; +import memory (1) from "mem/memory"; + +global mut global:shadow_prelude_panic.wado::__modules_initialized: bool = 0; + +fn "shadow_prelude_panic.wado/__cm_export____test_0_a_module_s_own_panic_outranks_the_prelude_s"() { + __inline___initialize_modules_0: block { + break_if __inline___initialize_modules_0 @likely(global:shadow_prelude_panic.wado::__modules_initialized); + cold_path; + "core:prelude/fpfmt.wado/__initialize_module"(); + global:shadow_prelude_panic.wado::__modules_initialized = 1; + }; + "wasi/task-return"(0); +} + +fn "shadow_prelude_panic.wado/__cm_export____test_1_a_module_s_own_unreachable_outranks_the_prelude_s"() { + __inline___initialize_modules_0: block { + break_if __inline___initialize_modules_0 @likely(global:shadow_prelude_panic.wado::__modules_initialized); + cold_path; + "core:prelude/fpfmt.wado/__initialize_module"(); + global:shadow_prelude_panic.wado::__modules_initialized = 1; + }; + "wasi/task-return"(0); +} + +fn "core:prelude/fpfmt.wado/__initialize_module"() { +} + +export fn "shadow_prelude_panic.wado/__cm_export____test_0_a_module_s_own_panic_outranks_the_prelude_s" as "__test_0_a_module_s_own_panic_outranks_the_prelude_s" +export fn "shadow_prelude_panic.wado/__cm_export____test_1_a_module_s_own_unreachable_outranks_the_prelude_s" as "__test_1_a_module_s_own_unreachable_outranks_the_prelude_s"