ensure import resolution is done according to local bindings

Might it be from a module that has multiple path fragments or one that is aliased.

Signed-off-by: KtorZ <matthias.benkort@gmail.com>
This commit is contained in:
KtorZ 2025-03-15 23:50:51 +01:00
parent 983902fca8
commit 2adc1fab66
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
3 changed files with 122 additions and 5 deletions

View File

@ -2296,7 +2296,7 @@ fn use_imported_type_as_namespace_for_patterns() {
"#; "#;
let result = check_with_deps( let result = check_with_deps(
dbg!(parse(source_code)), parse(source_code),
vec![(parse_as(dependency, "cardano/address"))], vec![(parse_as(dependency, "cardano/address"))],
); );
@ -2443,6 +2443,52 @@ fn use_type_as_nested_namespace_for_constructors() {
assert!(matches!(result, Ok(..)), "{result:#?}"); assert!(matches!(result, Ok(..)), "{result:#?}");
} }
#[test]
fn use_type_as_nested_namespace_for_constructors_from_multi_level_module() {
let dependency = r#"
pub type Foo {
I(Int)
B(Bool)
}
"#;
let source_code = r#"
use foo/bar
test my_test() {
trace bar.Foo.I(42)
Void
}
"#;
let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo/bar"))]);
assert!(matches!(result, Ok(..)), "{result:#?}");
}
#[test]
fn use_type_as_nested_namespace_for_constructors_from_module_alias() {
let dependency = r#"
pub type Foo {
I(Int)
B(Bool)
}
"#;
let source_code = r#"
use foo as bar
test my_test() {
trace bar.Foo.I(42)
Void
}
"#;
let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]);
assert!(matches!(result, Ok(..)), "{result:#?}");
}
#[test] #[test]
fn use_type_as_namespace_unknown_constructor() { fn use_type_as_namespace_unknown_constructor() {
let dependency = r#" let dependency = r#"
@ -2626,6 +2672,33 @@ fn use_opaque_type_as_nested_namespace_for_constructors_fails() {
); );
} }
#[test]
fn use_non_imported_module_as_namespace() {
let dependency = r#"
pub type Foo {
I(Int)
B(Bool)
}
"#;
let source_code = r#"
test my_test() {
trace foo.Foo.I(14)
Void
}
"#;
let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]);
assert!(
matches!(
&result,
Err((warnings, Error::UnknownModule { name, .. })) if name == "foo" && warnings.is_empty(),
),
"{result:#?}"
);
}
#[test] #[test]
fn forbid_importing_or_using_opaque_constructors() { fn forbid_importing_or_using_opaque_constructors() {
let dependency = r#" let dependency = r#"

View File

@ -372,6 +372,31 @@ impl<'a> Environment<'a> {
} }
} }
/// Get an imported module's actual name in the current module, from its fully qualified module
/// name.
#[allow(clippy::result_large_err)]
pub fn local_module_name(&self, name: &str, location: Span) -> Result<String, Error> {
self.imported_modules
.iter()
.filter_map(|(k, module)| {
if module.1.name == name {
Some(k.to_string())
} else {
None
}
})
.next()
.ok_or_else(|| Error::UnknownModule {
location,
name: name.to_string(),
known_modules: self
.importable_modules
.keys()
.map(|t| t.to_string())
.collect(),
})
}
#[allow(clippy::result_large_err)] #[allow(clippy::result_large_err)]
pub fn get_fully_qualified_value_constructor( pub fn get_fully_qualified_value_constructor(
&self, &self,
@ -379,8 +404,8 @@ impl<'a> Environment<'a> {
(type_name, type_location): (&str, Span), (type_name, type_location): (&str, Span),
(value, value_location): (&str, Span), (value, value_location): (&str, Span),
) -> Result<&ValueConstructor, Error> { ) -> Result<&ValueConstructor, Error> {
let (_, module) = let module =
self.imported_modules self.importable_modules
.get(module_name) .get(module_name)
.ok_or_else(|| Error::UnknownModule { .ok_or_else(|| Error::UnknownModule {
location: module_location, location: module_location,

View File

@ -1095,8 +1095,25 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
location: module_location, location: module_location,
} = type_container.as_ref() } = type_container.as_ref()
{ {
// Lookup the module using the declared name (which may have been rebind with
// 'as'), to obtain its _full unambiguous name_.
let (_, module) = self
.environment
.imported_modules
.get(module_name)
.ok_or_else(|| Error::UnknownModule {
location: *module_location,
name: module_name.to_string(),
known_modules: self
.environment
.importable_modules
.keys()
.map(|t| t.to_string())
.collect(),
})?;
return self.infer_inner_type_constructor_access( return self.infer_inner_type_constructor_access(
(module_name, *module_location), (module.name.as_str(), *module_location),
( (
type_name, type_name,
type_location.map(|start, end| (start + module_name.len() + 1, end)), type_location.map(|start, end| (start + module_name.len() + 1, end)),
@ -1251,7 +1268,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
self.environment.unused_modules.remove(module_name); self.environment.unused_modules.remove(module_name);
self.infer_module_access( self.infer_module_access(
module_name, &self
.environment
.local_module_name(module_name, module_location)?,
label.to_string(), label.to_string(),
&type_location, &type_location,
label_location, label_location,