iter 23.4-prep.2: check — bare-name fall-through to implicit-imported free fns
This commit is contained in:
@@ -1806,6 +1806,34 @@ pub(crate) fn synth(
|
||||
method: name.clone(),
|
||||
});
|
||||
return Ok(inst_ty);
|
||||
} else if let Some((owner_module, raw_ty)) = env
|
||||
.imports
|
||||
.values()
|
||||
.find_map(|mod_name| {
|
||||
env.module_globals
|
||||
.get(mod_name)
|
||||
.and_then(|mg| mg.get(name).map(|ty| (mod_name.clone(), ty.clone())))
|
||||
})
|
||||
{
|
||||
// Iter 23.4-prep: bare-name fall-through to free fns of
|
||||
// implicitly-imported modules. Today only the prelude is
|
||||
// implicitly imported (see `build_check_env` and
|
||||
// `check_in_workspace`'s prelude-injection at this file's
|
||||
// ~1170 / ~1285), so the first match is unambiguous. If a
|
||||
// second implicit import lands, the single-match-wins rule
|
||||
// becomes a real ambiguity and this branch needs a
|
||||
// duplicate-detection diagnostic.
|
||||
//
|
||||
// Qualify any bare type-cons referring to a type defined in
|
||||
// the owning module — same treatment the dot-qualified arm
|
||||
// below applies — so signatures cross the module boundary
|
||||
// with fully-qualified Type::Cons.
|
||||
let owner_types = env
|
||||
.module_types
|
||||
.get(&owner_module)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
qualify_local_types(&raw_ty, &owner_module, &owner_types)
|
||||
} else if name.matches('.').count() == 1 {
|
||||
let (prefix, suffix) = name.split_once('.').expect("checked");
|
||||
let target_module = match env.imports.get(prefix) {
|
||||
@@ -4935,4 +4963,67 @@ mod tests {
|
||||
let diags = check_workspace(&ws);
|
||||
assert!(diags.is_empty(), "expected green; got {diags:?}");
|
||||
}
|
||||
|
||||
/// Iter 23.4-prep Task 2: bare-name resolution falls through to free
|
||||
/// fns of implicitly-imported modules. The consumer calls bare `dbl`,
|
||||
/// declared in `prelude` (the singleton implicit import today). The
|
||||
/// pre-fix behaviour is `unbound-var` because the `Term::Var` lookup
|
||||
/// ladder consults locals → local globals → class_methods →
|
||||
/// dot-qualified, but never the flat free-fn tables of implicit
|
||||
/// imports.
|
||||
#[test]
|
||||
fn bare_name_resolves_through_implicit_import_to_free_fn() {
|
||||
let prelude = Module {
|
||||
schema: SCHEMA.into(),
|
||||
name: "prelude".into(),
|
||||
imports: vec![],
|
||||
defs: vec![fn_def(
|
||||
"dbl",
|
||||
Type::Fn {
|
||||
params: vec![Type::int()],
|
||||
ret: Box::new(Type::int()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
},
|
||||
vec!["n"],
|
||||
Term::Var { name: "n".into() },
|
||||
)],
|
||||
};
|
||||
let consumer = Module {
|
||||
schema: SCHEMA.into(),
|
||||
name: "m".into(),
|
||||
imports: vec![],
|
||||
defs: vec![fn_def(
|
||||
"main",
|
||||
Type::Fn {
|
||||
params: vec![],
|
||||
ret: Box::new(Type::int()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
},
|
||||
vec![],
|
||||
Term::App {
|
||||
callee: Box::new(Term::Var { name: "dbl".into() }),
|
||||
args: vec![Term::Lit { lit: Literal::Int { value: 5 } }],
|
||||
tail: false,
|
||||
},
|
||||
)],
|
||||
};
|
||||
let mut modules = BTreeMap::new();
|
||||
modules.insert("prelude".into(), prelude);
|
||||
modules.insert("m".into(), consumer);
|
||||
let ws = Workspace {
|
||||
entry: "m".into(),
|
||||
modules,
|
||||
root_dir: std::path::PathBuf::from("."),
|
||||
registry: ailang_core::workspace::Registry::default(),
|
||||
};
|
||||
let diags = check_workspace(&ws);
|
||||
assert!(
|
||||
diags.is_empty(),
|
||||
"expected bare `dbl` to resolve through implicit prelude import; got {diags:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user