diff --git a/crates/aiken-lang/src/tests/check.rs b/crates/aiken-lang/src/tests/check.rs index 1e4afa1b..790b625d 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -2296,7 +2296,7 @@ fn use_imported_type_as_namespace_for_patterns() { "#; let result = check_with_deps( - dbg!(parse(source_code)), + parse(source_code), vec![(parse_as(dependency, "cardano/address"))], ); @@ -2443,6 +2443,52 @@ fn use_type_as_nested_namespace_for_constructors() { 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] fn use_type_as_namespace_unknown_constructor() { 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] fn forbid_importing_or_using_opaque_constructors() { let dependency = r#" diff --git a/crates/aiken-lang/src/tipo/environment.rs b/crates/aiken-lang/src/tipo/environment.rs index ab5fce6c..e814c45f 100644 --- a/crates/aiken-lang/src/tipo/environment.rs +++ b/crates/aiken-lang/src/tipo/environment.rs @@ -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 { + 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)] pub fn get_fully_qualified_value_constructor( &self, @@ -379,8 +404,8 @@ impl<'a> Environment<'a> { (type_name, type_location): (&str, Span), (value, value_location): (&str, Span), ) -> Result<&ValueConstructor, Error> { - let (_, module) = - self.imported_modules + let module = + self.importable_modules .get(module_name) .ok_or_else(|| Error::UnknownModule { location: module_location, diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 8fa90958..c6a289ec 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -1095,8 +1095,25 @@ impl<'a, 'b> ExprTyper<'a, 'b> { location: module_location, } = 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( - (module_name, *module_location), + (module.name.as_str(), *module_location), ( type_name, 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.infer_module_access( - module_name, + &self + .environment + .local_module_name(module_name, module_location)?, label.to_string(), &type_location, label_location,