Authorize complete patterns as function args.
This is mainly a syntactic trick/sugar, but it's been pretty annoying
to me for a while that we can't simply pattern-match/destructure
single-variant constructors directly from the args list. A classic
example is when writing property tests:
```ak
test foo(params via both(bytearray(), int())) {
let (bytes, ix) = params
...
}
```
Now can be replaced simply with:
```
test foo((bytes, ix) via both(bytearray(), int())) {
...
}
```
If feels natural, especially coming from the JavaScript, Haskell or
Rust worlds and is mostly convenient. Behind the scene, the compiler
does nothing more than re-writing the AST as the first form, with
pre-generated arg names. Then, we fully rely on the existing
type-checking capabilities and thus, works in a seamless way as if we
were just pattern matching inline.
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
use crate::{
|
||||
ast,
|
||||
ast::ArgBy::ByName,
|
||||
expr::UntypedExpr,
|
||||
parser::{annotation, error::ParseError, expr, token::Token, utils},
|
||||
parser::{annotation, error::ParseError, expr, pattern, token::Token, utils},
|
||||
};
|
||||
use chumsky::prelude::*;
|
||||
|
||||
@@ -51,38 +50,45 @@ pub fn param(is_validator_param: bool) -> impl Parser<Token, ast::UntypedArg, Er
|
||||
choice((
|
||||
select! {Token::Name {name} => name}
|
||||
.then(select! {Token::DiscardName {name} => name})
|
||||
.map_with_span(|(label, name), span| ast::ArgName::Discarded {
|
||||
label,
|
||||
name,
|
||||
location: span,
|
||||
.map_with_span(|(label, name), span| {
|
||||
ast::ArgBy::ByName(ast::ArgName::Discarded {
|
||||
label,
|
||||
name,
|
||||
location: span,
|
||||
})
|
||||
}),
|
||||
select! {Token::DiscardName {name} => name}.map_with_span(|name, span| {
|
||||
ast::ArgName::Discarded {
|
||||
ast::ArgBy::ByName(ast::ArgName::Discarded {
|
||||
label: name.clone(),
|
||||
name,
|
||||
location: span,
|
||||
}
|
||||
})
|
||||
}),
|
||||
select! {Token::Name {name} => name}
|
||||
.then(select! {Token::Name {name} => name})
|
||||
.map_with_span(|(label, name), span| ast::ArgName::Named {
|
||||
label,
|
||||
.map_with_span(|(label, name), span| {
|
||||
ast::ArgBy::ByName(ast::ArgName::Named {
|
||||
label,
|
||||
name,
|
||||
location: span,
|
||||
})
|
||||
}),
|
||||
select! {Token::Name {name} => name}.map_with_span(|name, span| {
|
||||
ast::ArgBy::ByName(ast::ArgName::Named {
|
||||
label: name.clone(),
|
||||
name,
|
||||
location: span,
|
||||
}),
|
||||
select! {Token::Name {name} => name}.map_with_span(|name, span| ast::ArgName::Named {
|
||||
label: name.clone(),
|
||||
name,
|
||||
location: span,
|
||||
})
|
||||
}),
|
||||
pattern().map(ast::ArgBy::ByPattern),
|
||||
))
|
||||
.then(just(Token::Colon).ignore_then(annotation()).or_not())
|
||||
.map_with_span(move |(arg_name, annotation), span| ast::UntypedArg {
|
||||
.map_with_span(move |(by, annotation), span| ast::UntypedArg {
|
||||
location: span,
|
||||
annotation,
|
||||
doc: None,
|
||||
is_validator_param,
|
||||
by: ByName(arg_name),
|
||||
by,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -118,4 +124,36 @@ mod tests {
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_by_pattern_no_annotation() {
|
||||
assert_definition!(
|
||||
r#"
|
||||
fn foo(Foo { my_field }) {
|
||||
my_field * 2
|
||||
}
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_by_pattern_with_annotation() {
|
||||
assert_definition!(
|
||||
r#"
|
||||
fn foo(Foo { my_field }: Foo) {
|
||||
my_field * 2
|
||||
}
|
||||
"#
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn function_by_pattern_with_alias() {
|
||||
assert_definition!(
|
||||
r#"
|
||||
fn foo(Foo { my_field, .. } as x) {
|
||||
my_field * x.my_other_field
|
||||
}
|
||||
"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
source: crates/aiken-lang/src/parser/definition/function.rs
|
||||
description: "Code:\n\nfn foo(Foo { my_field }) {\n my_field * 2\n}\n"
|
||||
---
|
||||
Fn(
|
||||
Function {
|
||||
arguments: [
|
||||
UntypedArg {
|
||||
by: ByPattern(
|
||||
Constructor {
|
||||
is_record: true,
|
||||
location: 7..23,
|
||||
name: "Foo",
|
||||
arguments: [
|
||||
CallArg {
|
||||
label: Some(
|
||||
"my_field",
|
||||
),
|
||||
location: 13..21,
|
||||
value: Var {
|
||||
location: 13..21,
|
||||
name: "my_field",
|
||||
},
|
||||
},
|
||||
],
|
||||
module: None,
|
||||
constructor: (),
|
||||
spread_location: None,
|
||||
tipo: (),
|
||||
},
|
||||
),
|
||||
location: 7..23,
|
||||
annotation: None,
|
||||
doc: None,
|
||||
is_validator_param: false,
|
||||
},
|
||||
],
|
||||
body: BinOp {
|
||||
location: 31..43,
|
||||
name: MultInt,
|
||||
left: Var {
|
||||
location: 31..39,
|
||||
name: "my_field",
|
||||
},
|
||||
right: UInt {
|
||||
location: 42..43,
|
||||
value: "2",
|
||||
base: Decimal {
|
||||
numeric_underscore: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
doc: None,
|
||||
location: 0..24,
|
||||
name: "foo",
|
||||
public: false,
|
||||
return_annotation: None,
|
||||
return_type: (),
|
||||
end_position: 44,
|
||||
on_test_failure: FailImmediately,
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
source: crates/aiken-lang/src/parser/definition/function.rs
|
||||
description: "Code:\n\nfn foo(Foo { my_field, .. } as x) {\n my_field * x.my_other_field\n}\n"
|
||||
---
|
||||
Fn(
|
||||
Function {
|
||||
arguments: [
|
||||
UntypedArg {
|
||||
by: ByPattern(
|
||||
Assign {
|
||||
name: "x",
|
||||
location: 7..32,
|
||||
pattern: Constructor {
|
||||
is_record: true,
|
||||
location: 7..27,
|
||||
name: "Foo",
|
||||
arguments: [
|
||||
CallArg {
|
||||
label: Some(
|
||||
"my_field",
|
||||
),
|
||||
location: 13..21,
|
||||
value: Var {
|
||||
location: 13..21,
|
||||
name: "my_field",
|
||||
},
|
||||
},
|
||||
],
|
||||
module: None,
|
||||
constructor: (),
|
||||
spread_location: Some(
|
||||
23..25,
|
||||
),
|
||||
tipo: (),
|
||||
},
|
||||
},
|
||||
),
|
||||
location: 7..32,
|
||||
annotation: None,
|
||||
doc: None,
|
||||
is_validator_param: false,
|
||||
},
|
||||
],
|
||||
body: BinOp {
|
||||
location: 40..67,
|
||||
name: MultInt,
|
||||
left: Var {
|
||||
location: 40..48,
|
||||
name: "my_field",
|
||||
},
|
||||
right: FieldAccess {
|
||||
location: 51..67,
|
||||
label: "my_other_field",
|
||||
container: Var {
|
||||
location: 51..52,
|
||||
name: "x",
|
||||
},
|
||||
},
|
||||
},
|
||||
doc: None,
|
||||
location: 0..33,
|
||||
name: "foo",
|
||||
public: false,
|
||||
return_annotation: None,
|
||||
return_type: (),
|
||||
end_position: 68,
|
||||
on_test_failure: FailImmediately,
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
source: crates/aiken-lang/src/parser/definition/function.rs
|
||||
description: "Code:\n\nfn foo(Foo { my_field }: Foo) {\n my_field * 2\n}\n"
|
||||
---
|
||||
Fn(
|
||||
Function {
|
||||
arguments: [
|
||||
UntypedArg {
|
||||
by: ByPattern(
|
||||
Constructor {
|
||||
is_record: true,
|
||||
location: 7..23,
|
||||
name: "Foo",
|
||||
arguments: [
|
||||
CallArg {
|
||||
label: Some(
|
||||
"my_field",
|
||||
),
|
||||
location: 13..21,
|
||||
value: Var {
|
||||
location: 13..21,
|
||||
name: "my_field",
|
||||
},
|
||||
},
|
||||
],
|
||||
module: None,
|
||||
constructor: (),
|
||||
spread_location: None,
|
||||
tipo: (),
|
||||
},
|
||||
),
|
||||
location: 7..28,
|
||||
annotation: Some(
|
||||
Constructor {
|
||||
location: 25..28,
|
||||
module: None,
|
||||
name: "Foo",
|
||||
arguments: [],
|
||||
},
|
||||
),
|
||||
doc: None,
|
||||
is_validator_param: false,
|
||||
},
|
||||
],
|
||||
body: BinOp {
|
||||
location: 36..48,
|
||||
name: MultInt,
|
||||
left: Var {
|
||||
location: 36..44,
|
||||
name: "my_field",
|
||||
},
|
||||
right: UInt {
|
||||
location: 47..48,
|
||||
value: "2",
|
||||
base: Decimal {
|
||||
numeric_underscore: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
doc: None,
|
||||
location: 0..29,
|
||||
name: "foo",
|
||||
public: false,
|
||||
return_annotation: None,
|
||||
return_type: (),
|
||||
end_position: 49,
|
||||
on_test_failure: FailImmediately,
|
||||
},
|
||||
)
|
||||
@@ -7,6 +7,7 @@ use crate::{
|
||||
chain::{call::parser as call, field_access, tuple_index::parser as tuple_index, Chain},
|
||||
error::ParseError,
|
||||
expr::{self, bytearray, int as uint, list, string, tuple, var},
|
||||
pattern,
|
||||
token::Token,
|
||||
},
|
||||
};
|
||||
@@ -54,25 +55,28 @@ pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError
|
||||
pub fn via() -> impl Parser<Token, ast::UntypedArgVia, Error = ParseError> {
|
||||
choice((
|
||||
select! {Token::DiscardName {name} => name}.map_with_span(|name, span| {
|
||||
ast::ArgName::Discarded {
|
||||
ast::ArgBy::ByName(ast::ArgName::Discarded {
|
||||
label: name.clone(),
|
||||
name,
|
||||
location: span,
|
||||
}
|
||||
})
|
||||
}),
|
||||
select! {Token::Name {name} => name}.map_with_span(|name, location| ast::ArgName::Named {
|
||||
label: name.clone(),
|
||||
name,
|
||||
location,
|
||||
select! {Token::Name {name} => name}.map_with_span(|name, location| {
|
||||
ast::ArgBy::ByName(ast::ArgName::Named {
|
||||
label: name.clone(),
|
||||
name,
|
||||
location,
|
||||
})
|
||||
}),
|
||||
pattern().map(ast::ArgBy::ByPattern),
|
||||
))
|
||||
.then(just(Token::Colon).ignore_then(annotation()).or_not())
|
||||
.map_with_span(|(arg_name, annotation), location| (arg_name, annotation, location))
|
||||
.then_ignore(just(Token::Via))
|
||||
.then(fuzzer())
|
||||
.map(|((arg_name, annotation, location), via)| ast::ArgVia {
|
||||
.map(|((by, annotation, location), via)| ast::ArgVia {
|
||||
arg: ast::UntypedArg {
|
||||
by: ast::ArgBy::ByName(arg_name),
|
||||
by,
|
||||
annotation,
|
||||
location,
|
||||
doc: None,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
ast,
|
||||
expr::{FnStyle, UntypedExpr},
|
||||
parser::{annotation, error::ParseError, token::Token},
|
||||
parser::{annotation, error::ParseError, pattern, token::Token},
|
||||
};
|
||||
use chumsky::prelude::*;
|
||||
|
||||
@@ -32,25 +32,28 @@ pub fn params() -> impl Parser<Token, ast::UntypedArg, Error = ParseError> {
|
||||
// TODO: return a better error when a label is provided `UnexpectedLabel`
|
||||
choice((
|
||||
select! {Token::DiscardName {name} => name}.map_with_span(|name, span| {
|
||||
ast::ArgName::Discarded {
|
||||
ast::ArgBy::ByName(ast::ArgName::Discarded {
|
||||
label: name.clone(),
|
||||
name,
|
||||
location: span,
|
||||
}
|
||||
})
|
||||
}),
|
||||
select! {Token::Name {name} => name}.map_with_span(|name, span| ast::ArgName::Named {
|
||||
label: name.clone(),
|
||||
name,
|
||||
location: span,
|
||||
select! {Token::Name {name} => name}.map_with_span(|name, span| {
|
||||
ast::ArgBy::ByName(ast::ArgName::Named {
|
||||
label: name.clone(),
|
||||
name,
|
||||
location: span,
|
||||
})
|
||||
}),
|
||||
pattern().map(ast::ArgBy::ByPattern),
|
||||
))
|
||||
.then(just(Token::Colon).ignore_then(annotation()).or_not())
|
||||
.map_with_span(|(arg_name, annotation), span| ast::UntypedArg {
|
||||
.map_with_span(|(by, annotation), span| ast::UntypedArg {
|
||||
is_validator_param: false,
|
||||
location: span,
|
||||
annotation,
|
||||
doc: None,
|
||||
by: ast::ArgBy::ByName(arg_name),
|
||||
by,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -62,4 +65,19 @@ mod tests {
|
||||
fn anonymous_function_basic() {
|
||||
assert_expr!(r#"fn (a: Int) -> Int { a + 1 }"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn anonymous_function_by_pattern_no_annotation() {
|
||||
assert_expr!(r#"fn (Foo { my_field }) { my_field * 2 }"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn anonymous_function_by_pattern_with_annotation() {
|
||||
assert_expr!(r#"fn (Foo { my_field } : Foo) { my_field * 2 }"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn anonymous_function_by_pattern_with_alias() {
|
||||
assert_expr!(r#"fn (Foo { my_field, .. } as x) { my_field * my_other_field }"#);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
source: crates/aiken-lang/src/parser/expr/anonymous_function.rs
|
||||
description: "Code:\n\nfn (Foo { my_field }) { my_field * 2 }"
|
||||
---
|
||||
Fn {
|
||||
location: 0..38,
|
||||
fn_style: Plain,
|
||||
arguments: [
|
||||
UntypedArg {
|
||||
by: ByPattern(
|
||||
Constructor {
|
||||
is_record: true,
|
||||
location: 4..20,
|
||||
name: "Foo",
|
||||
arguments: [
|
||||
CallArg {
|
||||
label: Some(
|
||||
"my_field",
|
||||
),
|
||||
location: 10..18,
|
||||
value: Var {
|
||||
location: 10..18,
|
||||
name: "my_field",
|
||||
},
|
||||
},
|
||||
],
|
||||
module: None,
|
||||
constructor: (),
|
||||
spread_location: None,
|
||||
tipo: (),
|
||||
},
|
||||
),
|
||||
location: 4..20,
|
||||
annotation: None,
|
||||
doc: None,
|
||||
is_validator_param: false,
|
||||
},
|
||||
],
|
||||
body: BinOp {
|
||||
location: 24..36,
|
||||
name: MultInt,
|
||||
left: Var {
|
||||
location: 24..32,
|
||||
name: "my_field",
|
||||
},
|
||||
right: UInt {
|
||||
location: 35..36,
|
||||
value: "2",
|
||||
base: Decimal {
|
||||
numeric_underscore: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
return_annotation: None,
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
source: crates/aiken-lang/src/parser/expr/anonymous_function.rs
|
||||
description: "Code:\n\nfn (Foo { my_field, .. } as x) { my_field * my_other_field }"
|
||||
---
|
||||
Fn {
|
||||
location: 0..60,
|
||||
fn_style: Plain,
|
||||
arguments: [
|
||||
UntypedArg {
|
||||
by: ByPattern(
|
||||
Assign {
|
||||
name: "x",
|
||||
location: 4..29,
|
||||
pattern: Constructor {
|
||||
is_record: true,
|
||||
location: 4..24,
|
||||
name: "Foo",
|
||||
arguments: [
|
||||
CallArg {
|
||||
label: Some(
|
||||
"my_field",
|
||||
),
|
||||
location: 10..18,
|
||||
value: Var {
|
||||
location: 10..18,
|
||||
name: "my_field",
|
||||
},
|
||||
},
|
||||
],
|
||||
module: None,
|
||||
constructor: (),
|
||||
spread_location: Some(
|
||||
20..22,
|
||||
),
|
||||
tipo: (),
|
||||
},
|
||||
},
|
||||
),
|
||||
location: 4..29,
|
||||
annotation: None,
|
||||
doc: None,
|
||||
is_validator_param: false,
|
||||
},
|
||||
],
|
||||
body: BinOp {
|
||||
location: 33..58,
|
||||
name: MultInt,
|
||||
left: Var {
|
||||
location: 33..41,
|
||||
name: "my_field",
|
||||
},
|
||||
right: Var {
|
||||
location: 44..58,
|
||||
name: "my_other_field",
|
||||
},
|
||||
},
|
||||
return_annotation: None,
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
source: crates/aiken-lang/src/parser/expr/anonymous_function.rs
|
||||
description: "Code:\n\nfn (Foo { my_field } : Foo) { my_field * 2 }"
|
||||
---
|
||||
Fn {
|
||||
location: 0..44,
|
||||
fn_style: Plain,
|
||||
arguments: [
|
||||
UntypedArg {
|
||||
by: ByPattern(
|
||||
Constructor {
|
||||
is_record: true,
|
||||
location: 4..20,
|
||||
name: "Foo",
|
||||
arguments: [
|
||||
CallArg {
|
||||
label: Some(
|
||||
"my_field",
|
||||
),
|
||||
location: 10..18,
|
||||
value: Var {
|
||||
location: 10..18,
|
||||
name: "my_field",
|
||||
},
|
||||
},
|
||||
],
|
||||
module: None,
|
||||
constructor: (),
|
||||
spread_location: None,
|
||||
tipo: (),
|
||||
},
|
||||
),
|
||||
location: 4..26,
|
||||
annotation: Some(
|
||||
Constructor {
|
||||
location: 23..26,
|
||||
module: None,
|
||||
name: "Foo",
|
||||
arguments: [],
|
||||
},
|
||||
),
|
||||
doc: None,
|
||||
is_validator_param: false,
|
||||
},
|
||||
],
|
||||
body: BinOp {
|
||||
location: 30..42,
|
||||
name: MultInt,
|
||||
left: Var {
|
||||
location: 30..38,
|
||||
name: "my_field",
|
||||
},
|
||||
right: UInt {
|
||||
location: 41..42,
|
||||
value: "2",
|
||||
base: Decimal {
|
||||
numeric_underscore: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
return_annotation: None,
|
||||
}
|
||||
Reference in New Issue
Block a user