diff --git a/crates/aiken-lang/src/parser/chain/field_access.rs b/crates/aiken-lang/src/parser/chain/field_access.rs index 24ee5e7b..05c0708a 100644 --- a/crates/aiken-lang/src/parser/chain/field_access.rs +++ b/crates/aiken-lang/src/parser/chain/field_access.rs @@ -1,7 +1,6 @@ use super::Chain; use crate::{ ast::well_known, - expr::UntypedExpr, parser::{token::Token, ParseError}, }; use chumsky::prelude::*; @@ -11,25 +10,11 @@ pub(crate) fn parser() -> impl Parser { .ignore_then(choice(( select! { Token::Else => well_known::VALIDATOR_ELSE.to_string() }, select! { Token::Name { name } => name, }, + select! { Token::UpName { name } => name, }, ))) .map_with_span(Chain::FieldAccess) } -pub(crate) fn constructor() -> impl Parser { - select! {Token::Name { name } => name} - .map_with_span(|module, span| (module, span)) - .then_ignore(just(Token::Dot)) - .then(select! {Token::UpName { name } => name}) - .map_with_span(|((module, m_span), name), span| UntypedExpr::FieldAccess { - location: span, - label: name, - container: Box::new(UntypedExpr::Var { - location: m_span, - name: module, - }), - }) -} - #[cfg(test)] mod tests { use crate::assert_expr; diff --git a/crates/aiken-lang/src/parser/expr/chained.rs b/crates/aiken-lang/src/parser/expr/chained.rs index 13ea7940..2ac3ee32 100644 --- a/crates/aiken-lang/src/parser/expr/chained.rs +++ b/crates/aiken-lang/src/parser/expr/chained.rs @@ -53,7 +53,6 @@ pub fn chain_start<'a>( pair(expression.clone()), record_update(expression.clone()), record(expression.clone()), - field_access::constructor(), and_or_chain(expression.clone()), var(), tuple(expression.clone()), diff --git a/crates/aiken-lang/src/tests/check.rs b/crates/aiken-lang/src/tests/check.rs index d9a7b2b8..c8e67fda 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -2279,7 +2279,7 @@ fn use_type_as_namespace_for_patterns() { let source_code = r#" use thing.{Foo} - pub fn bar(foo: Foo) { + fn bar(foo: Foo) { when foo is { Foo.A -> True Foo.B -> False @@ -2335,8 +2335,6 @@ fn use_wrong_type_as_namespace_for_patterns_fails() { C D } - - "#; let source_code = r#" @@ -2361,6 +2359,237 @@ fn use_wrong_type_as_namespace_for_patterns_fails() { ); } +#[test] +fn use_type_as_namespace_for_constructors() { + let dependency = r#" + pub type Foo { + I(Int) + B(Bool) + } + "#; + + let source_code = r#" + use foo.{Foo} + + test my_test() { + and { + Foo.I(42) == foo.I(42), + foo.B(True) == Foo.B(True), + } + } + "#; + + let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); + + assert!(matches!(result, Ok(..)), "{result:#?}"); +} + +#[test] +fn use_type_as_nested_namespace_for_constructors() { + let dependency = r#" + pub type Foo { + I(Int) + B(Bool) + } + "#; + + let source_code = r#" + use foo + + test my_test() { + trace foo.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#" + pub type Foo { + I(Int) + B(Bool) + } + "#; + + let source_code = r#" + use foo.{Foo} + + test my_test() { + Foo.A == Foo.I(42) + } + "#; + + let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); + + assert!( + matches!( + &result, + Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "A" && warnings.is_empty(), + ), + "{result:#?}" + ); +} + +#[test] +fn use_wrong_type_as_namespace() { + let dependency = r#" + pub type Foo { + I(Int) + B(Bool) + } + + pub type Bar { + S(String) + L(List) + } + "#; + + let source_code = r#" + use foo.{Foo} + + test my_test() { + trace Foo.S(@"wut") + Void + } + "#; + + let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); + + assert!( + matches!( + &result, + Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "S" && warnings.is_empty(), + ), + "{result:#?}" + ); +} + +#[test] +fn use_wrong_nested_type_as_namespace() { + let dependency = r#" + pub type Foo { + I(Int) + B(Bool) + } + + pub type Bar { + S(String) + L(List) + } + "#; + + let source_code = r#" + use foo + + test my_test() { + trace foo.Foo.S(@"wut") + Void + } + "#; + + let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); + + assert!( + matches!( + &result, + Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "S" && warnings.is_empty(), + ), + "{result:#?}" + ); +} + +#[test] +fn use_private_type_as_nested_namespace_fails() { + let dependency = r#" + type Foo { + I(Int) + B(Bool) + } + "#; + + let source_code = r#" + use foo + + test my_test() { + trace foo.Foo.I(42) + Void + } + "#; + + let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); + + assert!( + matches!( + &result, + Err((warnings, Error::UnknownModuleType { name, .. })) if name == "Foo" && warnings.is_empty(), + ), + "{result:#?}" + ); +} + +#[test] +fn use_opaque_type_as_namespace_for_constructors_fails() { + let dependency = r#" + pub opaque type Foo { + I(Int) + B(Bool) + } + "#; + + let source_code = r#" + use foo.{Foo} + + test my_test() { + trace Foo.I(42) + Void + } + "#; + + let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); + + assert!( + matches!( + &result, + Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "I" && warnings.is_empty(), + ), + "{result:#?}" + ); +} + +#[test] +fn use_opaque_type_as_nested_namespace_for_constructors_fails() { + let dependency = r#" + pub opaque type Foo { + I(Int) + B(Bool) + } + "#; + + let source_code = r#" + use foo + + test my_test() { + trace foo.Foo.I(42) + Void + } + "#; + + let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "foo"))]); + + assert!( + matches!( + &result, + Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "I" && 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 988365de..6473ce91 100644 --- a/crates/aiken-lang/src/tipo/environment.rs +++ b/crates/aiken-lang/src/tipo/environment.rs @@ -372,6 +372,53 @@ impl<'a> Environment<'a> { } } + #[allow(clippy::result_large_err)] + pub fn get_fully_qualified_value_constructor( + &self, + (module_name, module_location): (&str, Span), + (type_name, type_location): (&str, Span), + (value, value_location): (&str, Span), + ) -> Result<&ValueConstructor, Error> { + let (_, module) = + self.imported_modules + .get(module_name) + .ok_or_else(|| Error::UnknownModule { + location: module_location, + name: module_name.to_string(), + known_modules: self + .importable_modules + .keys() + .map(|t| t.to_string()) + .collect(), + })?; + + let constructors = + module + .types_constructors + .get(type_name) + .ok_or_else(|| Error::UnknownModuleType { + location: type_location, + name: type_name.to_string(), + module_name: module.name.clone(), + type_constructors: module.types.keys().map(|t| t.to_string()).collect(), + })?; + + let unknown_type_constructor = || Error::UnknownTypeConstructor { + location: value_location, + name: value.to_string(), + constructors: constructors.clone(), + }; + + if !constructors.iter().any(|s| s.as_str() == value) { + return Err(unknown_type_constructor()); + } + + module + .values + .get(value) + .ok_or_else(unknown_type_constructor) + } + #[allow(clippy::result_large_err)] pub fn get_type_constructor_mut( &mut self, @@ -464,35 +511,13 @@ impl<'a> Environment<'a> { types: self.known_type_names(), })?; - let (_, module) = - self.imported_modules - .get(&parent_type.module) - .ok_or_else(|| Error::UnknownModule { - name: parent_type.module.to_string(), - known_modules: self - .importable_modules - .keys() - .map(|t| t.to_string()) - .collect(), - location, - })?; - self.unused_modules.remove(&parent_type.module); - let empty_vec = vec![]; - let constructors = module.types_constructors.get(t).unwrap_or(&empty_vec); - - let unknown_type_constructor = || Error::UnknownTypeConstructor { - location, - name: name.to_string(), - constructors: constructors.clone(), - }; - - if !constructors.iter().any(|constructor| constructor == name) { - return Err(unknown_type_constructor()); - } - - module.values.get(name).ok_or_else(unknown_type_constructor) + self.get_fully_qualified_value_constructor( + (parent_type.module.as_str(), location), + (t, location), + (name, location.map(|start, end| (start + t.len() + 1, end))), + ) } Some(Namespace::Module(m)) => { diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index e572fa85..483a9839 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -1068,6 +1068,41 @@ impl<'a, 'b> ExprTyper<'a, 'b> { return shortcircuit; } + // In case where we find an uppercase var name in a record access chain, we treat the type + // as a namespace and lookup the next constructor as if it were imported from the module's + // the type originally belong to. + match container { + UntypedExpr::Var { + name: ref type_name, + location: type_location, + } if TypeConstructor::might_be(type_name) => { + return self.infer_type_constructor_access( + (type_name, type_location), + (&label, access_location), + ); + } + + UntypedExpr::FieldAccess { + location: type_location, + label: ref type_name, + container: ref type_container, + } if TypeConstructor::might_be(type_name) => { + if let UntypedExpr::Var { + name: ref module_name, + location: module_location, + } = type_container.as_ref() + { + return self.infer_inner_type_constructor_access( + (module_name, *module_location), + (type_name, type_location), + (&label, access_location), + ); + } + } + + _ => (), + }; + // Attempt to infer the container as a record access. If that fails, we may be shadowing the name // of an imported module, so attempt to infer the container as a module access. // TODO: Remove this cloning @@ -1075,7 +1110,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { Ok(record_access) => Ok(record_access), Err(err) => match container { - UntypedExpr::Var { name, location } => { + UntypedExpr::Var { name, location } if !TypeConstructor::might_be(&name) => { let module_access = self.infer_module_access(&name, label, &location, access_location); @@ -1162,6 +1197,51 @@ impl<'a, 'b> ExprTyper<'a, 'b> { }) } + #[allow(clippy::result_large_err)] + fn infer_type_constructor_access( + &mut self, + (type_name, type_location): (&str, Span), + (label, label_location): (&str, Span), + ) -> Result { + let parent_type = self + .environment + .module_types + .get(type_name) + .ok_or_else(|| Error::UnknownType { + location: type_location, + name: type_name.to_string(), + types: self.environment.known_type_names(), + })?; + + let module_name = parent_type.module.clone(); + + self.infer_inner_type_constructor_access( + (module_name.as_str(), type_location), + (type_name, type_location), + (label, label_location), + ) + } + + #[allow(clippy::result_large_err)] + fn infer_inner_type_constructor_access( + &mut self, + (module_name, module_location): (&str, Span), + (type_name, type_location): (&str, Span), + (label, label_location): (&str, Span), + ) -> Result { + self.environment.get_fully_qualified_value_constructor( + (module_name, module_location), + (type_name, type_location), + (label, label_location), + )?; + self.infer_module_access( + module_name, + label.to_string(), + &type_location, + label_location, + ) + } + #[allow(clippy::result_large_err)] fn infer_record_access( &mut self, @@ -1169,9 +1249,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> { label: String, location: Span, ) -> Result { - // Infer the type of the (presumed) record + // Infer the type of the (presumed) record. let record = self.infer(record)?; - self.infer_known_record_access(record, label, location) }