diff --git a/crates/aiken-lang/src/parser/chain/field_access.rs b/crates/aiken-lang/src/parser/chain/field_access.rs index 86eab783..24ee5e7b 100644 --- a/crates/aiken-lang/src/parser/chain/field_access.rs +++ b/crates/aiken-lang/src/parser/chain/field_access.rs @@ -1,5 +1,6 @@ use super::Chain; use crate::{ + ast::well_known, expr::UntypedExpr, parser::{token::Token, ParseError}, }; @@ -8,7 +9,7 @@ use chumsky::prelude::*; pub(crate) fn parser() -> impl Parser { just(Token::Dot) .ignore_then(choice(( - select! { Token::Else => "else".to_string() }, + select! { Token::Else => well_known::VALIDATOR_ELSE.to_string() }, select! { Token::Name { name } => name, }, ))) .map_with_span(Chain::FieldAccess) diff --git a/crates/aiken-lang/src/parser/pattern/constructor.rs b/crates/aiken-lang/src/parser/pattern/constructor.rs index a0336fd2..ebfa681e 100644 --- a/crates/aiken-lang/src/parser/pattern/constructor.rs +++ b/crates/aiken-lang/src/parser/pattern/constructor.rs @@ -1,29 +1,51 @@ use chumsky::prelude::*; use crate::{ - ast::{CallArg, Span, UntypedPattern}, + ast::{CallArg, Namespace, Span, UntypedPattern}, parser::{error::ParseError, token::Token}, }; pub fn parser( pattern: Recursive<'_, Token, UntypedPattern, ParseError>, ) -> impl Parser + '_ { - select! {Token::UpName { name } => name} - .then(args(pattern)) - .map_with_span( - |(name, (arguments, spread_location, is_record)), location| { - UntypedPattern::Constructor { - is_record, - location, - name, - arguments, - module: None, - constructor: (), - spread_location, - tipo: (), - } - }, - ) + choice(( + select! { Token::UpName { name } => name } + .then( + just(Token::Dot).ignore_then( + select! {Token::UpName { name } => name}.then(args(pattern.clone())), + ), + ) + .map_with_span( + |(namespace, (name, (arguments, spread_location, is_record))), span| { + UntypedPattern::Constructor { + is_record, + location: span, + name, + arguments, + module: Some(Namespace::Type(namespace)), + constructor: (), + spread_location, + tipo: (), + } + }, + ), + select! {Token::UpName { name } => name} + .then(args(pattern)) + .map_with_span( + |(name, (arguments, spread_location, is_record)), location| { + UntypedPattern::Constructor { + is_record, + location, + name, + arguments, + module: None, + constructor: (), + spread_location, + tipo: (), + } + }, + ), + )) } pub(crate) fn args( @@ -102,4 +124,9 @@ mod tests { fn constructor_module_select() { assert_pattern!("module.Foo"); } + + #[test] + fn constructor_type_select() { + assert_pattern!("Foo.Bar"); + } } diff --git a/crates/aiken-lang/src/parser/pattern/snapshots/constructor_type_select.snap b/crates/aiken-lang/src/parser/pattern/snapshots/constructor_type_select.snap new file mode 100644 index 00000000..468e6816 --- /dev/null +++ b/crates/aiken-lang/src/parser/pattern/snapshots/constructor_type_select.snap @@ -0,0 +1,18 @@ +--- +source: crates/aiken-lang/src/parser/pattern/constructor.rs +description: "Code:\n\nFoo.Bar" +--- +Constructor { + is_record: false, + location: 0..7, + name: "Bar", + arguments: [], + module: Some( + Type( + "Foo", + ), + ), + constructor: (), + spread_location: None, + tipo: (), +} diff --git a/crates/aiken-lang/src/parser/pattern/var.rs b/crates/aiken-lang/src/parser/pattern/var.rs index c278f825..a75bc87f 100644 --- a/crates/aiken-lang/src/parser/pattern/var.rs +++ b/crates/aiken-lang/src/parser/pattern/var.rs @@ -9,31 +9,33 @@ use crate::{ pub fn parser( expression: Recursive<'_, Token, UntypedPattern, ParseError>, ) -> impl Parser + '_ { - select! { Token::Name {name} => name } - .then( - just(Token::Dot) - .ignore_then( - select! {Token::UpName { name } => name}.then(constructor::args(expression)), - ) - .or_not(), - ) - .map_with_span(|(name, opt_pattern), span| { - if let Some((c_name, (arguments, spread_location, is_record))) = opt_pattern { - UntypedPattern::Constructor { - is_record, - location: span, - name: c_name, - arguments, - module: Some(name), - constructor: (), - spread_location, - tipo: (), - } - } else { - UntypedPattern::Var { - location: span, - name, - } + select! { + Token::Name {name} => name, + } + .then( + just(Token::Dot) + .ignore_then( + select! { Token::UpName { name } => name }.then(constructor::args(expression)), + ) + .or_not(), + ) + .map_with_span(|(name, opt_pattern), span| { + if let Some((c_name, (arguments, spread_location, is_record))) = opt_pattern { + UntypedPattern::Constructor { + is_record, + location: span, + name: c_name, + arguments, + module: Some(crate::ast::Namespace::Module(name)), + constructor: (), + spread_location, + tipo: (), } - }) + } else { + UntypedPattern::Var { + location: span, + name, + } + } + }) } diff --git a/crates/aiken-lang/src/tests/check.rs b/crates/aiken-lang/src/tests/check.rs index 9410b801..4fb8617c 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -2254,6 +2254,100 @@ fn allow_expect_into_opaque_type_constructor_without_typecasting_in_module() { assert!(check(parse(source_code)).is_ok()); } +#[test] +fn use_type_as_namespace_for_patterns() { + let dependency = r#" + pub type Foo { + A + B + } + "#; + + let source_code = r#" + use thing.{Foo} + + pub fn bar(foo: Foo) { + when foo is { + Foo.A -> True + Foo.B -> False + } + } + "#; + + let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "thing"))]); + + assert!(matches!(result, Ok(..)), "{result:#?}"); +} + +#[test] +fn use_opaque_type_as_namespace_for_patterns_fails() { + let dependency = r#" + pub opaque type Foo { + A + B + } + "#; + + let source_code = r#" + use thing.{Foo} + + fn bar(foo: Foo) { + when foo is { + Foo.A -> True + Foo.B -> False + } + } + "#; + + let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "thing"))]); + + assert!( + matches!( + &result, + Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "A" && warnings.is_empty(), + ), + "{result:#?}" + ); +} + +#[test] +fn use_wrong_type_as_namespace_for_patterns_fails() { + let dependency = r#" + pub type Foo { + A + B + } + + pub type Bar { + C + D + } + + + "#; + + let source_code = r#" + use thing.{Foo} + + fn bar(foo: Foo) { + when foo is { + Foo.A -> True + Foo.D -> False + } + } + "#; + + let result = check_with_deps(parse(source_code), vec![(parse_as(dependency, "thing"))]); + + assert!( + matches!( + &result, + Err((warnings, Error::UnknownTypeConstructor { name, .. })) if name == "D" && 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 5c75f3d1..988365de 100644 --- a/crates/aiken-lang/src/tipo/environment.rs +++ b/crates/aiken-lang/src/tipo/environment.rs @@ -378,7 +378,7 @@ impl<'a> Environment<'a> { name: &str, location: Span, ) -> Result<&mut TypeConstructor, Error> { - let types = self.module_types.keys().map(|t| t.to_string()).collect(); + let types = self.known_type_names(); let constructor = self .module_types @@ -407,7 +407,7 @@ impl<'a> Environment<'a> { .ok_or_else(|| Error::UnknownType { location, name: name.to_string(), - types: self.module_types.keys().map(|t| t.to_string()).collect(), + types: self.known_type_names(), }), Some(m) => { @@ -457,8 +457,42 @@ impl<'a> Environment<'a> { constructors: self.local_constructor_names(), }), - Some(Namespace::Type(..)) => { - todo!() + Some(Namespace::Type(t)) => { + let parent_type = self.module_types.get(t).ok_or_else(|| Error::UnknownType { + location, + name: t.to_string(), + 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) } Some(Namespace::Module(m)) => { @@ -732,6 +766,22 @@ impl<'a> Environment<'a> { } } + /// Get a list of known type names, for suggestions in errors. + pub fn known_type_names(&self) -> Vec { + self.module_types + .keys() + .filter_map(|t| { + // Avoid leaking special internal types such as __ScriptContext or + // __ScriptPurpose. + if t.starts_with("__") { + None + } else { + Some(t.to_string()) + } + }) + .collect() + } + pub fn local_value_names(&self) -> Vec { self.scope .keys() @@ -1820,7 +1870,7 @@ impl<'a> Environment<'a> { .get(name) .ok_or_else(|| Error::UnknownType { name: name.to_string(), - types: self.module_types.keys().map(|t| t.to_string()).collect(), + types: self.known_type_names(), location, })? .iter()