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:
parent
b6da42baf2
commit
858dfccc82
|
@ -2,6 +2,10 @@
|
|||
|
||||
## v1.0.30-alpha - UNRELEASED
|
||||
|
||||
### Added
|
||||
|
||||
- **aiken-lang**: also authorize (complete) patterns in function arguments list instead of only variable names. @KtorZ
|
||||
|
||||
### Changed
|
||||
|
||||
- **aiken-lang**: duplicate import lines are now automatically merged instead of raising a warning. However, imports can no longer appear anywhere in the file and must come as the first definitions. @KtorZ
|
||||
|
|
|
@ -15,7 +15,7 @@ use std::{
|
|||
rc::Rc,
|
||||
};
|
||||
use uplc::machine::runtime::Compressable;
|
||||
use vec1::Vec1;
|
||||
use vec1::{vec1, Vec1};
|
||||
|
||||
pub const BACKPASS_VARIABLE: &str = "_backpass";
|
||||
pub const CAPTURE_VARIABLE: &str = "_capture";
|
||||
|
@ -797,6 +797,32 @@ pub enum ArgBy {
|
|||
ByPattern(UntypedPattern),
|
||||
}
|
||||
|
||||
impl ArgBy {
|
||||
pub fn into_extra_assignment(
|
||||
self,
|
||||
name: &ArgName,
|
||||
annotation: Option<&Annotation>,
|
||||
location: Span,
|
||||
) -> Option<UntypedExpr> {
|
||||
match self {
|
||||
ArgBy::ByName(..) => None,
|
||||
ArgBy::ByPattern(pattern) => Some(UntypedExpr::Assignment {
|
||||
location,
|
||||
value: Box::new(UntypedExpr::Var {
|
||||
location,
|
||||
name: name.get_name(),
|
||||
}),
|
||||
patterns: vec1![AssignmentPattern {
|
||||
pattern,
|
||||
location,
|
||||
annotation: annotation.cloned(),
|
||||
}],
|
||||
kind: AssignmentKind::Let { backpassing: false },
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct UntypedArg {
|
||||
pub by: ArgBy,
|
||||
|
|
|
@ -464,15 +464,17 @@ impl<'comments> Formatter<'comments> {
|
|||
|
||||
let doc_comments = self.doc_comments(arg.location.start);
|
||||
|
||||
let doc = match arg.by {
|
||||
ArgBy::ByName(ref arg_name) => match &arg.annotation {
|
||||
None => arg_name.to_doc(),
|
||||
Some(a) => arg_name.to_doc().append(": ").append(self.annotation(a)),
|
||||
}
|
||||
.group(),
|
||||
ArgBy::ByPattern(..) => todo!(),
|
||||
let mut doc = match arg.by {
|
||||
ArgBy::ByName(ref arg_name) => arg_name.to_doc(),
|
||||
ArgBy::ByPattern(ref pattern) => self.pattern(pattern),
|
||||
};
|
||||
|
||||
doc = match &arg.annotation {
|
||||
None => doc,
|
||||
Some(a) => doc.append(": ").append(self.annotation(a)),
|
||||
}
|
||||
.group();
|
||||
|
||||
let doc = doc_comments.append(doc.group()).group();
|
||||
|
||||
commented(doc, comments)
|
||||
|
@ -483,12 +485,14 @@ impl<'comments> Formatter<'comments> {
|
|||
|
||||
let doc_comments = self.doc_comments(arg_via.arg.location.start);
|
||||
|
||||
let doc = match arg_via.arg.by {
|
||||
ArgBy::ByName(ref arg_name) => match &arg_via.arg.annotation {
|
||||
None => arg_name.to_doc(),
|
||||
Some(a) => arg_name.to_doc().append(": ").append(self.annotation(a)),
|
||||
},
|
||||
ArgBy::ByPattern(..) => todo!(),
|
||||
let mut doc = match arg_via.arg.by {
|
||||
ArgBy::ByName(ref arg_name) => arg_name.to_doc(),
|
||||
ArgBy::ByPattern(ref pattern) => self.pattern(pattern),
|
||||
};
|
||||
|
||||
doc = match &arg_via.arg.annotation {
|
||||
None => doc,
|
||||
Some(a) => doc.append(": ").append(self.annotation(a)),
|
||||
}
|
||||
.append(" via ")
|
||||
.append(self.expr(&arg_via.via, false))
|
||||
|
@ -1981,7 +1985,11 @@ impl<'a> Documentable<'a> for &'a ArgName {
|
|||
}
|
||||
|
||||
fn pub_(public: bool) -> Document<'static> {
|
||||
if public { "pub ".to_doc() } else { nil() }
|
||||
if public {
|
||||
"pub ".to_doc()
|
||||
} else {
|
||||
nil()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Documentable<'a> for &'a UnqualifiedImport {
|
||||
|
|
|
@ -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 {
|
||||
.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 {
|
||||
.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::ArgName::Named {
|
||||
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(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 {
|
||||
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 {
|
||||
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,
|
||||
}
|
|
@ -2539,3 +2539,37 @@ fn mutually_recursive_1() {
|
|||
|
||||
assert!(check(parse(source_code)).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_single_variant_pattern() {
|
||||
let source_code = r#"
|
||||
pub type Foo {
|
||||
a: Int
|
||||
}
|
||||
|
||||
pub fn foo(Foo { a }) {
|
||||
a + 1
|
||||
}
|
||||
"#;
|
||||
|
||||
assert!(dbg!(check(parse(source_code))).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fn_multi_variant_pattern() {
|
||||
let source_code = r#"
|
||||
type Foo {
|
||||
A { a: Int }
|
||||
B { b: Int }
|
||||
}
|
||||
|
||||
pub fn foo(A { a }) {
|
||||
a + 1
|
||||
}
|
||||
"#;
|
||||
|
||||
assert!(matches!(
|
||||
dbg!(check_validator(parse(source_code))),
|
||||
Err((_, Error::NotExhaustivePatternMatch { .. }))
|
||||
))
|
||||
}
|
||||
|
|
|
@ -883,3 +883,68 @@ fn format_pairs() {
|
|||
}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_fn_pattern() {
|
||||
assert_format!(
|
||||
r#"
|
||||
pub fn foo(Foo { a, b, .. }) {
|
||||
todo
|
||||
}
|
||||
|
||||
pub fn bar([Bar] : List<Bar>) {
|
||||
todo
|
||||
}
|
||||
|
||||
pub fn baz((Baz, Baz) as x) {
|
||||
todo
|
||||
}
|
||||
|
||||
pub fn fiz(Pair(fst, snd) as x: Pair<Int, Int>) {
|
||||
todo
|
||||
}
|
||||
|
||||
test buz((a, b) via some_fuzzer()) {
|
||||
todo
|
||||
}
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_anon_fn_pattern() {
|
||||
assert_format!(
|
||||
r#"
|
||||
pub fn main() {
|
||||
let foo = fn (Foo { a, b, .. }) { todo }
|
||||
let bar = fn ([Bar] : List<Bar>) { todo }
|
||||
let baz = fn ((Baz, Baz) as x) { todo }
|
||||
let fiz = fn (Pair(fst, snd) as x: Pair<Int, Int>) { todo }
|
||||
todo
|
||||
}
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_validator_pattern() {
|
||||
assert_format!(
|
||||
r#"
|
||||
validator(Foo { a, b, .. }) {
|
||||
fn foo() { todo }
|
||||
}
|
||||
|
||||
validator([Bar] : List<Bar>) {
|
||||
fn bar() { todo }
|
||||
}
|
||||
|
||||
validator((Baz, Baz) as x) {
|
||||
fn baz() { todo }
|
||||
}
|
||||
|
||||
validator((fst, snd) as x: Pair<Int, Int>) {
|
||||
fn fiz() { todo }
|
||||
}
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
source: crates/aiken-lang/src/tests/format.rs
|
||||
description: "Code:\n\npub fn main() {\n let foo = fn (Foo { a, b, .. }) { todo }\n let bar = fn ([Bar] : List<Bar>) { todo }\n let baz = fn ((Baz, Baz) as x) { todo }\n let fiz = fn (Pair(fst, snd) as x: Pair<Int, Int>) { todo }\n todo\n}\n"
|
||||
---
|
||||
pub fn main() {
|
||||
let foo =
|
||||
fn(Foo { a, b, .. }) {
|
||||
todo
|
||||
}
|
||||
let bar =
|
||||
fn([Bar]: List<Bar>) {
|
||||
todo
|
||||
}
|
||||
let baz =
|
||||
fn((Baz, Baz) as x) {
|
||||
todo
|
||||
}
|
||||
let fiz =
|
||||
fn(Pair(fst, snd) as x: Pair<Int, Int>) {
|
||||
todo
|
||||
}
|
||||
todo
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
source: crates/aiken-lang/src/tests/format.rs
|
||||
description: "Code:\n\npub fn foo(Foo { a, b, .. }) {\n todo\n}\n\npub fn bar([Bar] : List<Bar>) {\n todo\n}\n\npub fn baz((Baz, Baz) as x) {\n todo\n}\n\npub fn fiz(Pair(fst, snd) as x: Pair<Int, Int>) {\n todo\n}\n\ntest buz((a, b) via some_fuzzer()) {\n todo\n}\n"
|
||||
---
|
||||
pub fn foo(Foo { a, b, .. }) {
|
||||
todo
|
||||
}
|
||||
|
||||
pub fn bar([Bar]: List<Bar>) {
|
||||
todo
|
||||
}
|
||||
|
||||
pub fn baz((Baz, Baz) as x) {
|
||||
todo
|
||||
}
|
||||
|
||||
pub fn fiz(Pair(fst, snd) as x: Pair<Int, Int>) {
|
||||
todo
|
||||
}
|
||||
|
||||
test buz((a, b) via some_fuzzer()) {
|
||||
todo
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
source: crates/aiken-lang/src/tests/format.rs
|
||||
description: "Code:\n\nvalidator(Foo { a, b, .. }) {\n fn foo() { todo }\n}\n\nvalidator([Bar] : List<Bar>) {\n fn bar() { todo }\n}\n\nvalidator((Baz, Baz) as x) {\n fn baz() { todo }\n}\n\nvalidator((fst, snd) as x: Pair<Int, Int>) {\n fn fiz() { todo }\n}\n"
|
||||
---
|
||||
validator(Foo { a, b, .. }) {
|
||||
fn foo() {
|
||||
todo
|
||||
}
|
||||
}
|
||||
|
||||
validator([Bar]: List<Bar>) {
|
||||
fn bar() {
|
||||
todo
|
||||
}
|
||||
}
|
||||
|
||||
validator((Baz, Baz) as x) {
|
||||
fn baz() {
|
||||
todo
|
||||
}
|
||||
}
|
||||
|
||||
validator((fst, snd) as x: Pair<Int, Int>) {
|
||||
fn fiz() {
|
||||
todo
|
||||
}
|
||||
}
|
|
@ -59,6 +59,39 @@ pub(crate) fn infer_function(
|
|||
return_type: _,
|
||||
} = fun;
|
||||
|
||||
let mut extra_let_assignments = Vec::new();
|
||||
for (i, arg) in arguments.iter().enumerate() {
|
||||
let let_assignment = arg.by.clone().into_extra_assignment(
|
||||
&arg.arg_name(i),
|
||||
arg.annotation.as_ref(),
|
||||
arg.location,
|
||||
);
|
||||
match let_assignment {
|
||||
None => {}
|
||||
Some(expr) => extra_let_assignments.push(expr),
|
||||
}
|
||||
}
|
||||
|
||||
let sequence;
|
||||
|
||||
let body = if extra_let_assignments.is_empty() {
|
||||
body
|
||||
} else if let UntypedExpr::Sequence { expressions, .. } = body {
|
||||
extra_let_assignments.extend(expressions.clone());
|
||||
sequence = UntypedExpr::Sequence {
|
||||
expressions: extra_let_assignments,
|
||||
location: *location,
|
||||
};
|
||||
&sequence
|
||||
} else {
|
||||
extra_let_assignments.extend([body.clone()]);
|
||||
sequence = UntypedExpr::Sequence {
|
||||
expressions: extra_let_assignments,
|
||||
location: body.location(),
|
||||
};
|
||||
&sequence
|
||||
};
|
||||
|
||||
let preregistered_fn = environment
|
||||
.get_variable(name)
|
||||
.expect("Could not find preregistered type for function");
|
||||
|
@ -331,9 +364,13 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
|
||||
let mut arguments = Vec::new();
|
||||
|
||||
let mut extra_let_assignments = Vec::new();
|
||||
for (i, arg) in args.into_iter().enumerate() {
|
||||
let arg = self.infer_param(arg, expected_args.get(i).cloned(), i)?;
|
||||
|
||||
let (arg, extra_let_assignment) =
|
||||
self.infer_param(arg, expected_args.get(i).cloned(), i)?;
|
||||
if let Some(expr) = extra_let_assignment {
|
||||
extra_let_assignments.push(expr);
|
||||
}
|
||||
arguments.push(arg);
|
||||
}
|
||||
|
||||
|
@ -342,6 +379,28 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
None => None,
|
||||
};
|
||||
|
||||
let body_location = body.location();
|
||||
|
||||
let body = if extra_let_assignments.is_empty() {
|
||||
body
|
||||
} else if let UntypedExpr::Sequence {
|
||||
location,
|
||||
expressions,
|
||||
} = body
|
||||
{
|
||||
extra_let_assignments.extend(expressions);
|
||||
UntypedExpr::Sequence {
|
||||
expressions: extra_let_assignments,
|
||||
location,
|
||||
}
|
||||
} else {
|
||||
extra_let_assignments.extend([body]);
|
||||
UntypedExpr::Sequence {
|
||||
expressions: extra_let_assignments,
|
||||
location: body_location,
|
||||
}
|
||||
};
|
||||
|
||||
self.infer_fn_with_known_types(arguments, body, return_type)
|
||||
}
|
||||
|
||||
|
@ -1071,13 +1130,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
})
|
||||
}
|
||||
|
||||
// TODO: Handle arg pattern
|
||||
fn infer_param(
|
||||
&mut self,
|
||||
untyped_arg: UntypedArg,
|
||||
expected: Option<Rc<Type>>,
|
||||
ix: usize,
|
||||
) -> Result<TypedArg, Error> {
|
||||
) -> Result<(TypedArg, Option<UntypedExpr>), Error> {
|
||||
let arg_name = untyped_arg.arg_name(ix);
|
||||
|
||||
let UntypedArg {
|
||||
|
@ -1102,14 +1160,18 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
self.unify(expected, tipo.clone(), location, false)?;
|
||||
}
|
||||
|
||||
Ok(TypedArg {
|
||||
let extra_assignment = by.into_extra_assignment(&arg_name, annotation.as_ref(), location);
|
||||
|
||||
let typed_arg = TypedArg {
|
||||
arg_name,
|
||||
location,
|
||||
annotation,
|
||||
tipo,
|
||||
is_validator_param,
|
||||
doc,
|
||||
})
|
||||
};
|
||||
|
||||
Ok((typed_arg, extra_assignment))
|
||||
}
|
||||
|
||||
fn infer_assignment(
|
||||
|
|
|
@ -507,10 +507,8 @@ impl Prng {
|
|||
fn as_prng(cst: &PlutusData) -> Prng {
|
||||
if let PlutusData::Constr(Constr { tag, fields, .. }) = cst {
|
||||
if *tag == 121 + Prng::SEEDED {
|
||||
if let [
|
||||
PlutusData::BoundedBytes(bytes),
|
||||
PlutusData::BoundedBytes(choices),
|
||||
] = &fields[..]
|
||||
if let [PlutusData::BoundedBytes(bytes), PlutusData::BoundedBytes(choices)] =
|
||||
&fields[..]
|
||||
{
|
||||
return Prng::Seeded {
|
||||
choices: choices.to_vec(),
|
||||
|
@ -1089,11 +1087,9 @@ impl TryFrom<TypedExpr> for Assertion<TypedExpr> {
|
|||
final_else,
|
||||
..
|
||||
} => {
|
||||
if let [
|
||||
IfBranch {
|
||||
if let [IfBranch {
|
||||
condition, body, ..
|
||||
},
|
||||
] = &branches[..]
|
||||
}] = &branches[..]
|
||||
{
|
||||
let then_is_true = match body {
|
||||
TypedExpr::Var {
|
||||
|
@ -1512,14 +1508,13 @@ mod test {
|
|||
}
|
||||
"#});
|
||||
|
||||
assert!(
|
||||
prop.run::<()>(
|
||||
assert!(prop
|
||||
.run::<()>(
|
||||
42,
|
||||
PropertyTest::DEFAULT_MAX_SUCCESS,
|
||||
&PlutusVersion::default()
|
||||
)
|
||||
.is_success()
|
||||
);
|
||||
.is_success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
# This file was generated by Aiken
|
||||
# You typically do not need to edit this file
|
||||
|
||||
[[requirements]]
|
||||
name = "aiken-lang/stdlib"
|
||||
version = "main"
|
||||
source = "github"
|
||||
|
||||
[[requirements]]
|
||||
name = "aiken-lang/fuzz"
|
||||
version = "main"
|
||||
source = "github"
|
||||
|
||||
[[packages]]
|
||||
name = "aiken-lang/stdlib"
|
||||
version = "main"
|
||||
requirements = []
|
||||
source = "github"
|
||||
|
||||
[[packages]]
|
||||
name = "aiken-lang/fuzz"
|
||||
version = "main"
|
||||
requirements = []
|
||||
source = "github"
|
||||
|
||||
[etags]
|
||||
"aiken-lang/fuzz@main" = [{ secs_since_epoch = 1717767691, nanos_since_epoch = 206091000 }, "98cf81aa68f9ccf68bc5aba9be06d06cb1db6e8eff60b668ed5e8ddf3588206b"]
|
||||
"aiken-lang/stdlib@main" = [{ secs_since_epoch = 1717767690, nanos_since_epoch = 920449000 }, "a746f5b5cd3c2ca5dc19c43bcfc64230c546fafea2ba5f8e340c227b85886078"]
|
|
@ -0,0 +1,21 @@
|
|||
name = "aiken-lang/104"
|
||||
version = "0.0.0"
|
||||
compiler = "v1.0.29-alpha"
|
||||
plutus = "v2"
|
||||
license = "Apache-2.0"
|
||||
description = "Aiken contracts for project 'aiken-lang/104'"
|
||||
|
||||
[repository]
|
||||
user = "aiken-lang"
|
||||
project = "104"
|
||||
platform = "github"
|
||||
|
||||
[[dependencies]]
|
||||
name = "aiken-lang/stdlib"
|
||||
version = "main"
|
||||
source = "github"
|
||||
|
||||
[[dependencies]]
|
||||
name = "aiken-lang/fuzz"
|
||||
version = "main"
|
||||
source = "github"
|
|
@ -0,0 +1,80 @@
|
|||
{
|
||||
"preamble": {
|
||||
"title": "aiken-lang/104",
|
||||
"description": "Aiken contracts for project 'aiken-lang/104'",
|
||||
"version": "0.0.0",
|
||||
"plutusVersion": "v2",
|
||||
"compiler": {
|
||||
"name": "Aiken",
|
||||
"version": "v1.0.29-alpha+257bd23"
|
||||
},
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"validators": [
|
||||
{
|
||||
"title": "tests.foo_3",
|
||||
"redeemer": {
|
||||
"title": "_data",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Data"
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"title": "th_arg",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/tests~1Foo"
|
||||
}
|
||||
}
|
||||
],
|
||||
"compiledCode": "582401000032323222253330043370e6eb4c018c014dd5001a400429309b2b2b9a5573cae841",
|
||||
"hash": "047dafbc61fb4a550a28398bde3680c48ff2000cf1022efc883124cd"
|
||||
}
|
||||
],
|
||||
"definitions": {
|
||||
"Bool": {
|
||||
"title": "Bool",
|
||||
"anyOf": [
|
||||
{
|
||||
"title": "False",
|
||||
"dataType": "constructor",
|
||||
"index": 0,
|
||||
"fields": []
|
||||
},
|
||||
{
|
||||
"title": "True",
|
||||
"dataType": "constructor",
|
||||
"index": 1,
|
||||
"fields": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"Data": {
|
||||
"title": "Data",
|
||||
"description": "Any Plutus data."
|
||||
},
|
||||
"Int": {
|
||||
"dataType": "integer"
|
||||
},
|
||||
"tests/Foo": {
|
||||
"title": "Foo",
|
||||
"anyOf": [
|
||||
{
|
||||
"title": "Foo",
|
||||
"dataType": "constructor",
|
||||
"index": 0,
|
||||
"fields": [
|
||||
{
|
||||
"title": "a0",
|
||||
"$ref": "#/definitions/Int"
|
||||
},
|
||||
{
|
||||
"title": "a1",
|
||||
"$ref": "#/definitions/Bool"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
use aiken/fuzz
|
||||
|
||||
type Foo {
|
||||
a0: Int,
|
||||
a1: Bool,
|
||||
}
|
||||
|
||||
fn foo_1(Foo { a0, .. }) -> Int {
|
||||
a0 + 1
|
||||
}
|
||||
|
||||
fn foo_2(Foo { a0, a1 } as foo) -> Int {
|
||||
if a1 {
|
||||
a0 + 1
|
||||
} else {
|
||||
foo.a0 - 1
|
||||
}
|
||||
}
|
||||
|
||||
validator(Foo { a0, .. }: Foo) {
|
||||
fn foo_3(_data, _redeemer) {
|
||||
a0 == 1
|
||||
}
|
||||
}
|
||||
|
||||
test example_1() {
|
||||
foo_1(Foo { a0: 1, a1: False }) == 2
|
||||
}
|
||||
|
||||
test example_2() {
|
||||
foo_2(Foo { a0: 1, a1: False }) == 0
|
||||
}
|
||||
|
||||
test example_3() {
|
||||
foo_3(Foo { a0: 1, a1: False }, "", "")
|
||||
}
|
||||
|
||||
test example_4() {
|
||||
let foo_4 = fn(Foo { a1, .. }) { a1 }
|
||||
foo_4(Foo { a0: 1, a1: True })
|
||||
}
|
||||
|
||||
test example_5((a, b) via fuzz.both(fuzz.int(), fuzz.int())) {
|
||||
a + b == b + a
|
||||
}
|
Loading…
Reference in New Issue