allow types to be used as namespace in patterns.
Signed-off-by: KtorZ <matthias.benkort@gmail.com>
This commit is contained in:
parent
c556ada7d5
commit
81713d93a0
|
@ -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)
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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: (),
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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#"
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue