allow types to be used as namespace in patterns.

Signed-off-by: KtorZ <matthias.benkort@gmail.com>
This commit is contained in:
KtorZ 2025-03-15 16:57:59 +01:00
parent c556ada7d5
commit 81713d93a0
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
6 changed files with 241 additions and 49 deletions

View File

@ -1,5 +1,6 @@
use super::Chain; use super::Chain;
use crate::{ use crate::{
ast::well_known,
expr::UntypedExpr, expr::UntypedExpr,
parser::{token::Token, ParseError}, parser::{token::Token, ParseError},
}; };
@ -8,7 +9,7 @@ use chumsky::prelude::*;
pub(crate) fn parser() -> impl Parser<Token, Chain, Error = ParseError> { pub(crate) fn parser() -> impl Parser<Token, Chain, Error = ParseError> {
just(Token::Dot) just(Token::Dot)
.ignore_then(choice(( .ignore_then(choice((
select! { Token::Else => "else".to_string() }, select! { Token::Else => well_known::VALIDATOR_ELSE.to_string() },
select! { Token::Name { name } => name, }, select! { Token::Name { name } => name, },
))) )))
.map_with_span(Chain::FieldAccess) .map_with_span(Chain::FieldAccess)

View File

@ -1,29 +1,51 @@
use chumsky::prelude::*; use chumsky::prelude::*;
use crate::{ use crate::{
ast::{CallArg, Span, UntypedPattern}, ast::{CallArg, Namespace, Span, UntypedPattern},
parser::{error::ParseError, token::Token}, parser::{error::ParseError, token::Token},
}; };
pub fn parser( pub fn parser(
pattern: Recursive<'_, Token, UntypedPattern, ParseError>, pattern: Recursive<'_, Token, UntypedPattern, ParseError>,
) -> impl Parser<Token, UntypedPattern, Error = ParseError> + '_ { ) -> impl Parser<Token, UntypedPattern, Error = ParseError> + '_ {
select! {Token::UpName { name } => name} choice((
.then(args(pattern)) select! { Token::UpName { name } => name }
.map_with_span( .then(
|(name, (arguments, spread_location, is_record)), location| { just(Token::Dot).ignore_then(
UntypedPattern::Constructor { select! {Token::UpName { name } => name}.then(args(pattern.clone())),
is_record, ),
location, )
name, .map_with_span(
arguments, |(namespace, (name, (arguments, spread_location, is_record))), span| {
module: None, UntypedPattern::Constructor {
constructor: (), is_record,
spread_location, location: span,
tipo: (), 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( pub(crate) fn args(
@ -102,4 +124,9 @@ mod tests {
fn constructor_module_select() { fn constructor_module_select() {
assert_pattern!("module.Foo"); assert_pattern!("module.Foo");
} }
#[test]
fn constructor_type_select() {
assert_pattern!("Foo.Bar");
}
} }

View File

@ -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: (),
}

View File

@ -9,31 +9,33 @@ use crate::{
pub fn parser( pub fn parser(
expression: Recursive<'_, Token, UntypedPattern, ParseError>, expression: Recursive<'_, Token, UntypedPattern, ParseError>,
) -> impl Parser<Token, UntypedPattern, Error = ParseError> + '_ { ) -> impl Parser<Token, UntypedPattern, Error = ParseError> + '_ {
select! { Token::Name {name} => name } select! {
.then( Token::Name {name} => name,
just(Token::Dot) }
.ignore_then( .then(
select! {Token::UpName { name } => name}.then(constructor::args(expression)), just(Token::Dot)
) .ignore_then(
.or_not(), select! { Token::UpName { name } => name }.then(constructor::args(expression)),
) )
.map_with_span(|(name, opt_pattern), span| { .or_not(),
if let Some((c_name, (arguments, spread_location, is_record))) = opt_pattern { )
UntypedPattern::Constructor { .map_with_span(|(name, opt_pattern), span| {
is_record, if let Some((c_name, (arguments, spread_location, is_record))) = opt_pattern {
location: span, UntypedPattern::Constructor {
name: c_name, is_record,
arguments, location: span,
module: Some(name), name: c_name,
constructor: (), arguments,
spread_location, module: Some(crate::ast::Namespace::Module(name)),
tipo: (), constructor: (),
} spread_location,
} else { tipo: (),
UntypedPattern::Var {
location: span,
name,
}
} }
}) } else {
UntypedPattern::Var {
location: span,
name,
}
}
})
} }

View File

@ -2254,6 +2254,100 @@ fn allow_expect_into_opaque_type_constructor_without_typecasting_in_module() {
assert!(check(parse(source_code)).is_ok()); 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] #[test]
fn forbid_importing_or_using_opaque_constructors() { fn forbid_importing_or_using_opaque_constructors() {
let dependency = r#" let dependency = r#"

View File

@ -378,7 +378,7 @@ impl<'a> Environment<'a> {
name: &str, name: &str,
location: Span, location: Span,
) -> Result<&mut TypeConstructor, Error> { ) -> 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 let constructor = self
.module_types .module_types
@ -407,7 +407,7 @@ impl<'a> Environment<'a> {
.ok_or_else(|| Error::UnknownType { .ok_or_else(|| Error::UnknownType {
location, location,
name: name.to_string(), name: name.to_string(),
types: self.module_types.keys().map(|t| t.to_string()).collect(), types: self.known_type_names(),
}), }),
Some(m) => { Some(m) => {
@ -457,8 +457,42 @@ impl<'a> Environment<'a> {
constructors: self.local_constructor_names(), constructors: self.local_constructor_names(),
}), }),
Some(Namespace::Type(..)) => { Some(Namespace::Type(t)) => {
todo!() 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)) => { 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<String> {
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<String> { pub fn local_value_names(&self) -> Vec<String> {
self.scope self.scope
.keys() .keys()
@ -1820,7 +1870,7 @@ impl<'a> Environment<'a> {
.get(name) .get(name)
.ok_or_else(|| Error::UnknownType { .ok_or_else(|| Error::UnknownType {
name: name.to_string(), name: name.to_string(),
types: self.module_types.keys().map(|t| t.to_string()).collect(), types: self.known_type_names(),
location, location,
})? })?
.iter() .iter()