diff --git a/crates/ail/src/main.rs b/crates/ail/src/main.rs index 780abf1..4915e28 100644 --- a/crates/ail/src/main.rs +++ b/crates/ail/src/main.rs @@ -1886,17 +1886,48 @@ fn resolve_describe_name<'ws>( if let Some(idx) = name.find('.') { let mod_name = &name[..idx]; let def_name = &name[idx + 1..]; - let m = ws.modules.get(mod_name).with_context(|| { - format!("no module `{mod_name}` in workspace `{}`", ws.entry) - })?; - let def = m - .defs - .iter() - .find(|d| d.name() == def_name) - .with_context(|| { - format!("no def `{def_name}` in module `{mod_name}`") - })?; - return Ok((mod_name.to_string(), def)); + // Precedence: a literal module wins. `.` resolves + // strictly through that module. + if let Some(m) = ws.modules.get(mod_name) { + let def = m + .defs + .iter() + .find(|d| d.name() == def_name) + .with_context(|| { + format!("no def `{def_name}` in module `{mod_name}`") + })?; + return Ok((mod_name.to_string(), def)); + } + // Canonical type-scoped form `.`: the left segment is + // not a module but a known TYPE. Resolve `` through the + // type's HOME module (the module that defines the `Def::Type` + // named `mod_name`) — same `Def` the module-scoped and bare + // forms return. First home module in iteration order wins; in + // practice a type name is unique across a workspace. + for (home_mod, m) in &ws.modules { + let defines_type = m + .defs + .iter() + .any(|d| matches!(d, ailang_core::Def::Type(_)) && d.name() == mod_name); + if !defines_type { + continue; + } + let def = m + .defs + .iter() + .find(|d| d.name() == def_name) + .with_context(|| { + format!( + "no def `{def_name}` in module `{home_mod}` \ + (home of type `{mod_name}`)" + ) + })?; + return Ok((home_mod.clone(), def)); + } + return Err(anyhow::anyhow!( + "no module or type `{mod_name}` in workspace `{}`", + ws.entry + )); } // Bare name: entry module first.