diff --git a/CHANGELOG.md b/CHANGELOG.md index 98aa26dd..e77243fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index fecbef99..a3220a1e 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -7,6 +7,7 @@ use crate::{ }; use indexmap::IndexMap; use miette::Diagnostic; +use ordinal::Ordinal; use owo_colors::{OwoColorize, Stream::Stdout}; use std::{ fmt::{self, Display}, @@ -14,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"; @@ -517,17 +518,17 @@ pub struct ModuleConstant { pub tipo: T, } -pub type TypedValidator = Validator, TypedExpr>; -pub type UntypedValidator = Validator<(), UntypedExpr>; +pub type TypedValidator = Validator, TypedArg, TypedExpr>; +pub type UntypedValidator = Validator<(), UntypedArg, UntypedExpr>; #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct Validator { +pub struct Validator { pub doc: Option, pub end_position: usize, - pub fun: Function>, - pub other_fun: Option>>, + pub fun: Function, + pub other_fun: Option>, pub location: Span, - pub params: Vec>, + pub params: Vec, } impl TypedValidator { @@ -575,12 +576,12 @@ impl TypedValidator { } } -pub type TypedDefinition = Definition, TypedExpr, String>; -pub type UntypedDefinition = Definition<(), UntypedExpr, ()>; +pub type TypedDefinition = Definition, TypedArg, TypedExpr, String>; +pub type UntypedDefinition = Definition<(), UntypedArg, UntypedExpr, ()>; #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub enum Definition { - Fn(Function>), +pub enum Definition { + Fn(Function), TypeAlias(TypeAlias), @@ -590,12 +591,12 @@ pub enum Definition { ModuleConstant(ModuleConstant), - Test(Function>), + Test(Function>), - Validator(Validator), + Validator(Validator), } -impl Definition { +impl Definition { pub fn location(&self) -> Span { match self { Definition::Fn(Function { location, .. }) @@ -790,27 +791,90 @@ impl RecordConstructorArg { } } -pub type TypedArg = Arg>; -pub type UntypedArg = Arg<()>; +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub enum ArgBy { + ByName(ArgName), + ByPattern(UntypedPattern), +} + +impl ArgBy { + pub fn into_extra_assignment( + self, + name: &ArgName, + annotation: Option<&Annotation>, + location: Span, + ) -> Option { + 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 Arg { +pub struct UntypedArg { + pub by: ArgBy, + pub location: Span, + pub annotation: Option, + pub doc: Option, + pub is_validator_param: bool, +} + +impl UntypedArg { + pub fn arg_name(&self, ix: usize) -> ArgName { + match self.by { + ArgBy::ByName(ref name) => name.clone(), + ArgBy::ByPattern(..) => { + // NOTE: We use ordinal here not only because it's cute, but because + // such a name cannot be parsed to begin with and thus, will not clash + // with any user-defined name. + let name = format!("{}_arg", Ordinal::(ix).suffix()); + ArgName::Named { + label: name.clone(), + name, + location: self.location, + } + } + } + } + + pub fn set_type(self, tipo: Rc, ix: usize) -> TypedArg { + TypedArg { + tipo, + arg_name: self.arg_name(ix), + location: self.location, + annotation: self.annotation, + is_validator_param: self.is_validator_param, + doc: self.doc, + } + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct TypedArg { pub arg_name: ArgName, pub location: Span, pub annotation: Option, pub doc: Option, - pub tipo: T, + pub is_validator_param: bool, + pub tipo: Rc, } -impl Arg { - pub fn set_type(self, tipo: B) -> Arg { - Arg { - tipo, - arg_name: self.arg_name, - location: self.location, - annotation: self.annotation, - doc: self.doc, - } +impl TypedArg { + pub fn put_doc(&mut self, new_doc: String) { + self.doc = Some(new_doc); } pub fn get_variable_name(&self) -> Option<&str> { @@ -830,12 +894,6 @@ impl Arg { false } - pub fn put_doc(&mut self, new_doc: String) { - self.doc = Some(new_doc); - } -} - -impl TypedArg { pub fn find_node(&self, byte_index: usize) -> Option> { if self.arg_name.location().contains(byte_index) { Some(Located::Argument(&self.arg_name, self.tipo.clone())) @@ -847,40 +905,38 @@ impl TypedArg { } } -pub type TypedArgVia = ArgVia, TypedExpr>; -pub type UntypedArgVia = ArgVia<(), UntypedExpr>; +pub type TypedArgVia = ArgVia; +pub type UntypedArgVia = ArgVia; #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct ArgVia { - pub arg_name: ArgName, - pub location: Span, +pub struct ArgVia { + pub arg: Arg, pub via: Expr, - pub tipo: T, - pub annotation: Option, } -impl From> for Arg { - fn from(arg: ArgVia) -> Arg { - Arg { - arg_name: arg.arg_name, - location: arg.location, - tipo: arg.tipo, - annotation: None, - doc: None, - } +impl From> for TypedArg { + fn from(this: ArgVia) -> TypedArg { + this.arg + } +} + +impl From> for UntypedArg { + fn from(this: ArgVia) -> UntypedArg { + this.arg } } impl TypedArgVia { pub fn find_node(&self, byte_index: usize) -> Option> { - if self.arg_name.location().contains(byte_index) { - Some(Located::Argument(&self.arg_name, self.tipo.clone())) + if self.arg.arg_name.location().contains(byte_index) { + Some(Located::Argument(&self.arg.arg_name, self.arg.tipo.clone())) } else { // `via` is done first here because when there is no manually written // annotation, it seems one is injected leading to a `found` returning too early // because the span of the filled in annotation matches the span of the via expr. self.via.find_node(byte_index).or_else(|| { - self.annotation + self.arg + .annotation .as_ref() .and_then(|annotation| annotation.find_node(byte_index)) }) @@ -899,7 +955,6 @@ pub enum ArgName { name: String, label: String, location: Span, - is_validator_param: bool, }, } diff --git a/crates/aiken-lang/src/builtins.rs b/crates/aiken-lang/src/builtins.rs index d5131909..0975965c 100644 --- a/crates/aiken-lang/src/builtins.rs +++ b/crates/aiken-lang/src/builtins.rs @@ -1,7 +1,7 @@ use crate::{ ast::{ - Annotation, Arg, ArgName, CallArg, DataTypeKey, Function, FunctionAccessKey, ModuleKind, - OnTestFailure, Span, TypedDataType, TypedFunction, UnOp, + Annotation, ArgName, CallArg, DataTypeKey, Function, FunctionAccessKey, ModuleKind, + OnTestFailure, Span, TypedArg, TypedDataType, TypedFunction, UnOp, }, expr::TypedExpr, tipo::{ @@ -932,13 +932,13 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap IndexMap IndexMap IndexMap IndexMap, is_capture: bool, - args: Vec>>, + args: Vec, body: Box, return_annotation: Option, }, @@ -495,7 +496,7 @@ pub enum UntypedExpr { Fn { location: Span, fn_style: FnStyle, - arguments: Vec>, + arguments: Vec, body: Box, return_annotation: Option, }, @@ -1192,17 +1193,16 @@ impl UntypedExpr { } => { let name = format!("{}__{index}", ast::CAPTURE_VARIABLE); - holes.push(ast::Arg { + holes.push(ast::UntypedArg { location: Span::empty(), annotation: None, doc: None, - arg_name: ast::ArgName::Named { + by: ArgBy::ByName(ast::ArgName::Named { label: name.clone(), name, location: Span::empty(), - is_validator_param: false, - }, - tipo: (), + }), + is_validator_param: false, }); ast::CallArg { @@ -1358,12 +1358,12 @@ impl UntypedExpr { fn_style: FnStyle::Plain, arguments: names .into_iter() - .map(|(arg_name, location, annotation)| Arg { + .map(|(arg_name, location, annotation)| UntypedArg { location, doc: None, annotation, - tipo: (), - arg_name, + is_validator_param: false, + by: ArgBy::ByName(arg_name), }) .collect(), body: Self::Sequence { diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index efa5ec89..c5a36dfc 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -1,6 +1,6 @@ use crate::{ ast::{ - Annotation, Arg, ArgName, ArgVia, AssignmentKind, AssignmentPattern, BinOp, + Annotation, ArgBy, ArgName, ArgVia, AssignmentKind, AssignmentPattern, BinOp, ByteArrayFormatPreference, CallArg, ClauseGuard, Constant, CurveType, DataType, Definition, Function, IfBranch, LogicalOpChainKind, ModuleConstant, OnTestFailure, Pattern, RecordConstructor, RecordConstructorArg, RecordUpdateSpread, Span, TraceKind, TypeAlias, @@ -459,18 +459,19 @@ impl<'comments> Formatter<'comments> { .append(line().append(self.annotation(typ)).group().nest(INDENT)) } - fn fn_arg<'a, A>(&mut self, arg: &'a Arg) -> Document<'a> { + fn fn_arg<'a>(&mut self, arg: &'a UntypedArg) -> Document<'a> { let comments = self.pop_comments(arg.location.start); let doc_comments = self.doc_comments(arg.location.start); - let doc = match &arg.annotation { - None => arg.arg_name.to_doc(), - Some(a) => arg - .arg_name - .to_doc() - .append(": ") - .append(self.annotation(a)), + 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(); @@ -479,21 +480,22 @@ impl<'comments> Formatter<'comments> { commented(doc, comments) } - fn fn_arg_via<'a, A>(&mut self, arg: &'a ArgVia) -> Document<'a> { - let comments = self.pop_comments(arg.location.start); + fn fn_arg_via<'a>(&mut self, arg_via: &'a ArgVia) -> Document<'a> { + let comments = self.pop_comments(arg_via.arg.location.start); - let doc_comments = self.doc_comments(arg.location.start); + let doc_comments = self.doc_comments(arg_via.arg.location.start); - let doc = match &arg.annotation { - None => arg.arg_name.to_doc(), - Some(a) => arg - .arg_name - .to_doc() - .append(": ") - .append(self.annotation(a)), + 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, false)) + .append(self.expr(&arg_via.via, false)) .group(); let doc = doc_comments.append(doc.group()).group(); diff --git a/crates/aiken-lang/src/parser/definition/function.rs b/crates/aiken-lang/src/parser/definition/function.rs index bfd68006..fe5f15ae 100644 --- a/crates/aiken-lang/src/parser/definition/function.rs +++ b/crates/aiken-lang/src/parser/definition/function.rs @@ -1,7 +1,7 @@ use crate::{ ast, expr::UntypedExpr, - parser::{annotation, error::ParseError, expr, token::Token, utils}, + parser::{annotation, error::ParseError, expr, pattern, token::Token, utils}, }; use chumsky::prelude::*; @@ -50,40 +50,45 @@ pub fn param(is_validator_param: bool) -> impl Parser 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(move |(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, - is_validator_param, - }), - select! {Token::Name {name} => name}.map_with_span(move |name, span| ast::ArgName::Named { - label: name.clone(), - name, - location: span, - is_validator_param, + }) }), + pattern().map(ast::ArgBy::ByPattern), )) .then(just(Token::Colon).ignore_then(annotation()).or_not()) - .map_with_span(|(arg_name, annotation), span| ast::Arg { + .map_with_span(move |(by, annotation), span| ast::UntypedArg { location: span, annotation, doc: None, - tipo: (), - arg_name, + is_validator_param, + by, }) } @@ -119,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 + } + "# + ); + } } diff --git a/crates/aiken-lang/src/parser/definition/snapshots/def_invalid_property_test.snap b/crates/aiken-lang/src/parser/definition/snapshots/def_invalid_property_test.snap index 3f6ec79c..b7b65666 100644 --- a/crates/aiken-lang/src/parser/definition/snapshots/def_invalid_property_test.snap +++ b/crates/aiken-lang/src/parser/definition/snapshots/def_invalid_property_test.snap @@ -6,34 +6,42 @@ Test( Function { arguments: [ ArgVia { - arg_name: Named { - name: "x", - label: "x", + arg: UntypedArg { + by: ByName( + Named { + name: "x", + label: "x", + location: 9..10, + }, + ), location: 9..10, + annotation: None, + doc: None, is_validator_param: false, }, - location: 9..10, via: Var { location: 15..16, name: "f", }, - tipo: (), - annotation: None, }, ArgVia { - arg_name: Named { - name: "y", - label: "y", + arg: UntypedArg { + by: ByName( + Named { + name: "y", + label: "y", + location: 18..19, + }, + ), location: 18..19, + annotation: None, + doc: None, is_validator_param: false, }, - location: 18..19, via: Var { location: 24..25, name: "g", }, - tipo: (), - annotation: None, }, ], body: Var { diff --git a/crates/aiken-lang/src/parser/definition/snapshots/def_property_test.snap b/crates/aiken-lang/src/parser/definition/snapshots/def_property_test.snap index 6674d603..f2ba0716 100644 --- a/crates/aiken-lang/src/parser/definition/snapshots/def_property_test.snap +++ b/crates/aiken-lang/src/parser/definition/snapshots/def_property_test.snap @@ -6,13 +6,19 @@ Test( Function { arguments: [ ArgVia { - arg_name: Named { - name: "x", - label: "x", + arg: UntypedArg { + by: ByName( + Named { + name: "x", + label: "x", + location: 9..10, + }, + ), location: 9..10, + annotation: None, + doc: None, is_validator_param: false, }, - location: 9..10, via: FieldAccess { location: 15..27, label: "any_int", @@ -21,8 +27,6 @@ Test( name: "fuzz", }, }, - tipo: (), - annotation: None, }, ], body: Var { diff --git a/crates/aiken-lang/src/parser/definition/snapshots/def_property_test_annotated_fuzzer.snap b/crates/aiken-lang/src/parser/definition/snapshots/def_property_test_annotated_fuzzer.snap index 12f065b5..721edd75 100644 --- a/crates/aiken-lang/src/parser/definition/snapshots/def_property_test_annotated_fuzzer.snap +++ b/crates/aiken-lang/src/parser/definition/snapshots/def_property_test_annotated_fuzzer.snap @@ -6,13 +6,26 @@ Test( Function { arguments: [ ArgVia { - arg_name: Named { - name: "x", - label: "x", - location: 9..10, + arg: UntypedArg { + by: ByName( + Named { + name: "x", + label: "x", + location: 9..10, + }, + ), + location: 9..15, + annotation: Some( + Constructor { + location: 12..15, + module: None, + name: "Int", + arguments: [], + }, + ), + doc: None, is_validator_param: false, }, - location: 9..15, via: Call { arguments: [], fun: Var { @@ -21,15 +34,6 @@ Test( }, location: 20..25, }, - tipo: (), - annotation: Some( - Constructor { - location: 12..15, - module: None, - name: "Int", - arguments: [], - }, - ), }, ], body: Var { diff --git a/crates/aiken-lang/src/parser/definition/snapshots/double_validator.snap b/crates/aiken-lang/src/parser/definition/snapshots/double_validator.snap index 95a4702a..5235bbdb 100644 --- a/crates/aiken-lang/src/parser/definition/snapshots/double_validator.snap +++ b/crates/aiken-lang/src/parser/definition/snapshots/double_validator.snap @@ -8,41 +8,44 @@ Validator( end_position: 90, fun: Function { arguments: [ - Arg { - arg_name: Named { - name: "datum", - label: "datum", - location: 21..26, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "datum", + label: "datum", + location: 21..26, + }, + ), location: 21..26, annotation: None, doc: None, - tipo: (), + is_validator_param: false, }, - Arg { - arg_name: Named { - name: "rdmr", - label: "rdmr", - location: 28..32, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "rdmr", + label: "rdmr", + location: 28..32, + }, + ), location: 28..32, annotation: None, doc: None, - tipo: (), + is_validator_param: false, }, - Arg { - arg_name: Named { - name: "ctx", - label: "ctx", - location: 34..37, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "ctx", + label: "ctx", + location: 34..37, + }, + ), location: 34..37, annotation: None, doc: None, - tipo: (), + is_validator_param: false, }, ], body: Var { @@ -61,29 +64,31 @@ Validator( other_fun: Some( Function { arguments: [ - Arg { - arg_name: Named { - name: "rdmr", - label: "rdmr", - location: 64..68, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "rdmr", + label: "rdmr", + location: 64..68, + }, + ), location: 64..68, annotation: None, doc: None, - tipo: (), + is_validator_param: false, }, - Arg { - arg_name: Named { - name: "ctx", - label: "ctx", - location: 70..73, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "ctx", + label: "ctx", + location: 70..73, + }, + ), location: 70..73, annotation: None, doc: None, - tipo: (), + is_validator_param: false, }, ], body: Var { diff --git a/crates/aiken-lang/src/parser/definition/snapshots/function_by_pattern_no_annotation.snap b/crates/aiken-lang/src/parser/definition/snapshots/function_by_pattern_no_annotation.snap new file mode 100644 index 00000000..359a9343 --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/function_by_pattern_no_annotation.snap @@ -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, + }, +) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/function_by_pattern_with_alias.snap b/crates/aiken-lang/src/parser/definition/snapshots/function_by_pattern_with_alias.snap new file mode 100644 index 00000000..69128a87 --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/function_by_pattern_with_alias.snap @@ -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, + }, +) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/function_by_pattern_with_annotation.snap b/crates/aiken-lang/src/parser/definition/snapshots/function_by_pattern_with_annotation.snap new file mode 100644 index 00000000..6b3da311 --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/function_by_pattern_with_annotation.snap @@ -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, + }, +) diff --git a/crates/aiken-lang/src/parser/definition/snapshots/validator.snap b/crates/aiken-lang/src/parser/definition/snapshots/validator.snap index 5f8e8463..fad9a357 100644 --- a/crates/aiken-lang/src/parser/definition/snapshots/validator.snap +++ b/crates/aiken-lang/src/parser/definition/snapshots/validator.snap @@ -8,41 +8,44 @@ Validator( end_position: 54, fun: Function { arguments: [ - Arg { - arg_name: Named { - name: "datum", - label: "datum", - location: 21..26, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "datum", + label: "datum", + location: 21..26, + }, + ), location: 21..26, annotation: None, doc: None, - tipo: (), + is_validator_param: false, }, - Arg { - arg_name: Named { - name: "rdmr", - label: "rdmr", - location: 28..32, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "rdmr", + label: "rdmr", + location: 28..32, + }, + ), location: 28..32, annotation: None, doc: None, - tipo: (), + is_validator_param: false, }, - Arg { - arg_name: Named { - name: "ctx", - label: "ctx", - location: 34..37, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "ctx", + label: "ctx", + location: 34..37, + }, + ), location: 34..37, annotation: None, doc: None, - tipo: (), + is_validator_param: false, }, ], body: Var { diff --git a/crates/aiken-lang/src/parser/definition/test.rs b/crates/aiken-lang/src/parser/definition/test.rs index 48330ede..0931c30d 100644 --- a/crates/aiken-lang/src/parser/definition/test.rs +++ b/crates/aiken-lang/src/parser/definition/test.rs @@ -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,31 +55,34 @@ pub fn parser() -> impl Parser impl Parser { 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(move |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, - is_validator_param: false, - } + }) }), + 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 { - arg_name, + .map(|((by, annotation, location), via)| ast::ArgVia { + arg: ast::UntypedArg { + by, + annotation, + location, + doc: None, + is_validator_param: false, + }, via, - annotation, - tipo: (), - location, }) } diff --git a/crates/aiken-lang/src/parser/expr/anonymous_binop.rs b/crates/aiken-lang/src/parser/expr/anonymous_binop.rs index db3b91e1..f4835eda 100644 --- a/crates/aiken-lang/src/parser/expr/anonymous_binop.rs +++ b/crates/aiken-lang/src/parser/expr/anonymous_binop.rs @@ -1,10 +1,9 @@ -use chumsky::prelude::*; - use crate::{ ast, expr::{FnStyle, UntypedExpr}, parser::{error::ParseError, token::Token}, }; +use chumsky::prelude::*; pub fn parser() -> impl Parser { select! { @@ -41,29 +40,27 @@ pub fn parser() -> impl Parser { }; let arguments = vec![ - ast::Arg { - arg_name: ast::ArgName::Named { + ast::UntypedArg { + by: ast::ArgBy::ByName(ast::ArgName::Named { name: "left".to_string(), label: "left".to_string(), location, - is_validator_param: false, - }, + }), + is_validator_param: false, annotation: arg_annotation.clone(), doc: None, location, - tipo: (), }, - ast::Arg { - arg_name: ast::ArgName::Named { + ast::UntypedArg { + by: ast::ArgBy::ByName(ast::ArgName::Named { name: "right".to_string(), label: "right".to_string(), location, - is_validator_param: false, - }, + }), + is_validator_param: false, annotation: arg_annotation, doc: None, location, - tipo: (), }, ]; diff --git a/crates/aiken-lang/src/parser/expr/anonymous_function.rs b/crates/aiken-lang/src/parser/expr/anonymous_function.rs index ba413534..39f35259 100644 --- a/crates/aiken-lang/src/parser/expr/anonymous_function.rs +++ b/crates/aiken-lang/src/parser/expr/anonymous_function.rs @@ -1,10 +1,9 @@ -use chumsky::prelude::*; - use crate::{ ast, expr::{FnStyle, UntypedExpr}, - parser::{annotation, error::ParseError, token::Token}, + parser::{annotation, error::ParseError, pattern, token::Token}, }; +use chumsky::prelude::*; pub fn parser( sequence: Recursive<'_, Token, UntypedExpr, ParseError>, @@ -33,26 +32,28 @@ pub fn params() -> impl Parser { // 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, - is_validator_param: false, + 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::Arg { + .map_with_span(|(by, annotation), span| ast::UntypedArg { + is_validator_param: false, location: span, annotation, doc: None, - tipo: (), - arg_name, + by, }) } @@ -64,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 }"#); + } } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_basic.snap b/crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_basic.snap index d8584847..e67b39f1 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_basic.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_basic.snap @@ -6,13 +6,14 @@ Fn { location: 0..28, fn_style: Plain, arguments: [ - Arg { - arg_name: Named { - name: "a", - label: "a", - location: 4..5, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "a", + label: "a", + location: 4..5, + }, + ), location: 4..10, annotation: Some( Constructor { @@ -23,7 +24,7 @@ Fn { }, ), doc: None, - tipo: (), + is_validator_param: false, }, ], body: BinOp { diff --git a/crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_body_sequence.snap b/crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_body_sequence.snap new file mode 100644 index 00000000..128e1d5f --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_body_sequence.snap @@ -0,0 +1,77 @@ +--- +source: crates/aiken-lang/src/parser/expr/anonymous_function.rs +description: "Code:\n\nfn (a: Int) -> Int {\n let b = 1\n a + b\n}" +--- +Fn { + location: 0..46, + fn_style: Plain, + arguments: [ + Arg { + arg_name: Named { + name: "a", + label: "a", + location: 4..5, + is_validator_param: false, + }, + location: 4..10, + annotation: Some( + Constructor { + location: 7..10, + module: None, + name: "Int", + arguments: [], + }, + ), + doc: None, + tipo: (), + }, + ], + body: Sequence { + location: 39..44, + expressions: [ + Assignment { + location: 25..34, + value: UInt { + location: 33..34, + value: "1", + base: Decimal { + numeric_underscore: false, + }, + }, + patterns: [ + AssignmentPattern { + pattern: Var { + location: 29..30, + name: "b", + }, + annotation: None, + location: 29..30, + }, + ], + kind: Let { + backpassing: false, + }, + }, + BinOp { + location: 39..44, + name: AddInt, + left: Var { + location: 39..40, + name: "a", + }, + right: Var { + location: 43..44, + name: "b", + }, + }, + ], + }, + return_annotation: Some( + Constructor { + location: 15..18, + module: None, + name: "Int", + arguments: [], + }, + ), +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_by_pattern_no_annotation.snap b/crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_by_pattern_no_annotation.snap new file mode 100644 index 00000000..5bb821d1 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_by_pattern_no_annotation.snap @@ -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, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_by_pattern_with_alias.snap b/crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_by_pattern_with_alias.snap new file mode 100644 index 00000000..4ac06aca --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_by_pattern_with_alias.snap @@ -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, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_by_pattern_with_annotation.snap b/crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_by_pattern_with_annotation.snap new file mode 100644 index 00000000..5a2251e8 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_by_pattern_with_annotation.snap @@ -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, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/first_class_binop.snap b/crates/aiken-lang/src/parser/expr/snapshots/first_class_binop.snap index 3a021757..8f90a37f 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/first_class_binop.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/first_class_binop.snap @@ -24,13 +24,14 @@ Sequence { GtInt, ), arguments: [ - Arg { - arg_name: Named { - name: "left", - label: "left", - location: 16..17, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "left", + label: "left", + location: 16..17, + }, + ), location: 16..17, annotation: Some( Constructor { @@ -41,15 +42,16 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, - Arg { - arg_name: Named { - name: "right", - label: "right", - location: 16..17, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "right", + label: "right", + location: 16..17, + }, + ), location: 16..17, annotation: Some( Constructor { @@ -60,7 +62,7 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, ], body: BinOp { @@ -119,13 +121,14 @@ Sequence { GtEqInt, ), arguments: [ - Arg { - arg_name: Named { - name: "left", - label: "left", - location: 38..40, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "left", + label: "left", + location: 38..40, + }, + ), location: 38..40, annotation: Some( Constructor { @@ -136,15 +139,16 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, - Arg { - arg_name: Named { - name: "right", - label: "right", - location: 38..40, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "right", + label: "right", + location: 38..40, + }, + ), location: 38..40, annotation: Some( Constructor { @@ -155,7 +159,7 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, ], body: BinOp { @@ -214,13 +218,14 @@ Sequence { LtInt, ), arguments: [ - Arg { - arg_name: Named { - name: "left", - label: "left", - location: 61..62, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "left", + label: "left", + location: 61..62, + }, + ), location: 61..62, annotation: Some( Constructor { @@ -231,15 +236,16 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, - Arg { - arg_name: Named { - name: "right", - label: "right", - location: 61..62, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "right", + label: "right", + location: 61..62, + }, + ), location: 61..62, annotation: Some( Constructor { @@ -250,7 +256,7 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, ], body: BinOp { @@ -309,13 +315,14 @@ Sequence { LtEqInt, ), arguments: [ - Arg { - arg_name: Named { - name: "left", - label: "left", - location: 83..85, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "left", + label: "left", + location: 83..85, + }, + ), location: 83..85, annotation: Some( Constructor { @@ -326,15 +333,16 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, - Arg { - arg_name: Named { - name: "right", - label: "right", - location: 83..85, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "right", + label: "right", + location: 83..85, + }, + ), location: 83..85, annotation: Some( Constructor { @@ -345,7 +353,7 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, ], body: BinOp { @@ -404,29 +412,31 @@ Sequence { Eq, ), arguments: [ - Arg { - arg_name: Named { - name: "left", - label: "left", - location: 106..108, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "left", + label: "left", + location: 106..108, + }, + ), location: 106..108, annotation: None, doc: None, - tipo: (), + is_validator_param: false, }, - Arg { - arg_name: Named { - name: "right", - label: "right", - location: 106..108, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "right", + label: "right", + location: 106..108, + }, + ), location: 106..108, annotation: None, doc: None, - tipo: (), + is_validator_param: false, }, ], body: BinOp { @@ -485,29 +495,31 @@ Sequence { NotEq, ), arguments: [ - Arg { - arg_name: Named { - name: "left", - label: "left", - location: 129..131, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "left", + label: "left", + location: 129..131, + }, + ), location: 129..131, annotation: None, doc: None, - tipo: (), + is_validator_param: false, }, - Arg { - arg_name: Named { - name: "right", - label: "right", - location: 129..131, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "right", + label: "right", + location: 129..131, + }, + ), location: 129..131, annotation: None, doc: None, - tipo: (), + is_validator_param: false, }, ], body: BinOp { @@ -566,13 +578,14 @@ Sequence { And, ), arguments: [ - Arg { - arg_name: Named { - name: "left", - label: "left", - location: 152..154, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "left", + label: "left", + location: 152..154, + }, + ), location: 152..154, annotation: Some( Constructor { @@ -583,15 +596,16 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, - Arg { - arg_name: Named { - name: "right", - label: "right", - location: 152..154, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "right", + label: "right", + location: 152..154, + }, + ), location: 152..154, annotation: Some( Constructor { @@ -602,7 +616,7 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, ], body: BinOp { @@ -661,13 +675,14 @@ Sequence { Or, ), arguments: [ - Arg { - arg_name: Named { - name: "left", - label: "left", - location: 175..177, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "left", + label: "left", + location: 175..177, + }, + ), location: 175..177, annotation: Some( Constructor { @@ -678,15 +693,16 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, - Arg { - arg_name: Named { - name: "right", - label: "right", - location: 175..177, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "right", + label: "right", + location: 175..177, + }, + ), location: 175..177, annotation: Some( Constructor { @@ -697,7 +713,7 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, ], body: BinOp { @@ -756,13 +772,14 @@ Sequence { AddInt, ), arguments: [ - Arg { - arg_name: Named { - name: "left", - label: "left", - location: 198..199, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "left", + label: "left", + location: 198..199, + }, + ), location: 198..199, annotation: Some( Constructor { @@ -773,15 +790,16 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, - Arg { - arg_name: Named { - name: "right", - label: "right", - location: 198..199, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "right", + label: "right", + location: 198..199, + }, + ), location: 198..199, annotation: Some( Constructor { @@ -792,7 +810,7 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, ], body: BinOp { @@ -851,13 +869,14 @@ Sequence { SubInt, ), arguments: [ - Arg { - arg_name: Named { - name: "left", - label: "left", - location: 220..221, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "left", + label: "left", + location: 220..221, + }, + ), location: 220..221, annotation: Some( Constructor { @@ -868,15 +887,16 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, - Arg { - arg_name: Named { - name: "right", - label: "right", - location: 220..221, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "right", + label: "right", + location: 220..221, + }, + ), location: 220..221, annotation: Some( Constructor { @@ -887,7 +907,7 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, ], body: BinOp { @@ -946,13 +966,14 @@ Sequence { DivInt, ), arguments: [ - Arg { - arg_name: Named { - name: "left", - label: "left", - location: 242..243, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "left", + label: "left", + location: 242..243, + }, + ), location: 242..243, annotation: Some( Constructor { @@ -963,15 +984,16 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, - Arg { - arg_name: Named { - name: "right", - label: "right", - location: 242..243, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "right", + label: "right", + location: 242..243, + }, + ), location: 242..243, annotation: Some( Constructor { @@ -982,7 +1004,7 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, ], body: BinOp { @@ -1041,13 +1063,14 @@ Sequence { MultInt, ), arguments: [ - Arg { - arg_name: Named { - name: "left", - label: "left", - location: 264..265, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "left", + label: "left", + location: 264..265, + }, + ), location: 264..265, annotation: Some( Constructor { @@ -1058,15 +1081,16 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, - Arg { - arg_name: Named { - name: "right", - label: "right", - location: 264..265, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "right", + label: "right", + location: 264..265, + }, + ), location: 264..265, annotation: Some( Constructor { @@ -1077,7 +1101,7 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, ], body: BinOp { @@ -1136,13 +1160,14 @@ Sequence { ModInt, ), arguments: [ - Arg { - arg_name: Named { - name: "left", - label: "left", - location: 286..287, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "left", + label: "left", + location: 286..287, + }, + ), location: 286..287, annotation: Some( Constructor { @@ -1153,15 +1178,16 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, - Arg { - arg_name: Named { - name: "right", - label: "right", - location: 286..287, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "right", + label: "right", + location: 286..287, + }, + ), location: 286..287, annotation: Some( Constructor { @@ -1172,7 +1198,7 @@ Sequence { }, ), doc: None, - tipo: (), + is_validator_param: false, }, ], body: BinOp { diff --git a/crates/aiken-lang/src/parser/expr/snapshots/function_invoke.snap b/crates/aiken-lang/src/parser/expr/snapshots/function_invoke.snap index b1e1a4d5..d3dc4cec 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/function_invoke.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/function_invoke.snap @@ -47,17 +47,18 @@ Sequence { location: 36..65, fn_style: Capture, arguments: [ - Arg { - arg_name: Named { - name: "_capture__0", - label: "_capture__0", - location: 0..0, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "_capture__0", + label: "_capture__0", + location: 0..0, + }, + ), location: 0..0, annotation: None, doc: None, - tipo: (), + is_validator_param: false, }, ], body: Call { @@ -77,17 +78,18 @@ Sequence { location: 48..64, fn_style: Plain, arguments: [ - Arg { - arg_name: Named { - name: "y", - label: "y", - location: 52..53, - is_validator_param: false, - }, + UntypedArg { + by: ByName( + Named { + name: "y", + label: "y", + location: 52..53, + }, + ), location: 52..53, annotation: None, doc: None, - tipo: (), + is_validator_param: false, }, ], body: BinOp { diff --git a/crates/aiken-lang/src/tests/check.rs b/crates/aiken-lang/src/tests/check.rs index e4d60b6c..b9d19b0b 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -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 { .. })) + )) +} diff --git a/crates/aiken-lang/src/tests/format.rs b/crates/aiken-lang/src/tests/format.rs index c8a5f0c2..a9d951db 100644 --- a/crates/aiken-lang/src/tests/format.rs +++ b/crates/aiken-lang/src/tests/format.rs @@ -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) { + todo + } + + pub fn baz((Baz, Baz) as x) { + todo + } + + pub fn fiz(Pair(fst, snd) as x: Pair) { + 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) { todo } + let baz = fn ((Baz, Baz) as x) { todo } + let fiz = fn (Pair(fst, snd) as x: Pair) { todo } + todo + } + "# + ); +} + +#[test] +fn format_validator_pattern() { + assert_format!( + r#" + validator(Foo { a, b, .. }) { + fn foo() { todo } + } + + validator([Bar] : List) { + fn bar() { todo } + } + + validator((Baz, Baz) as x) { + fn baz() { todo } + } + + validator((fst, snd) as x: Pair) { + fn fiz() { todo } + } + "# + ); +} diff --git a/crates/aiken-lang/src/tests/snapshots/format_anon_fn_pattern.snap b/crates/aiken-lang/src/tests/snapshots/format_anon_fn_pattern.snap new file mode 100644 index 00000000..00e712a7 --- /dev/null +++ b/crates/aiken-lang/src/tests/snapshots/format_anon_fn_pattern.snap @@ -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) { todo }\n let baz = fn ((Baz, Baz) as x) { todo }\n let fiz = fn (Pair(fst, snd) as x: Pair) { todo }\n todo\n}\n" +--- +pub fn main() { + let foo = + fn(Foo { a, b, .. }) { + todo + } + let bar = + fn([Bar]: List) { + todo + } + let baz = + fn((Baz, Baz) as x) { + todo + } + let fiz = + fn(Pair(fst, snd) as x: Pair) { + todo + } + todo +} diff --git a/crates/aiken-lang/src/tests/snapshots/format_fn_pattern.snap b/crates/aiken-lang/src/tests/snapshots/format_fn_pattern.snap new file mode 100644 index 00000000..7746e0fb --- /dev/null +++ b/crates/aiken-lang/src/tests/snapshots/format_fn_pattern.snap @@ -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) {\n todo\n}\n\npub fn baz((Baz, Baz) as x) {\n todo\n}\n\npub fn fiz(Pair(fst, snd) as x: Pair) {\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) { + todo +} + +pub fn baz((Baz, Baz) as x) { + todo +} + +pub fn fiz(Pair(fst, snd) as x: Pair) { + todo +} + +test buz((a, b) via some_fuzzer()) { + todo +} diff --git a/crates/aiken-lang/src/tests/snapshots/format_validator_pattern.snap b/crates/aiken-lang/src/tests/snapshots/format_validator_pattern.snap new file mode 100644 index 00000000..74023392 --- /dev/null +++ b/crates/aiken-lang/src/tests/snapshots/format_validator_pattern.snap @@ -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) {\n fn bar() { todo }\n}\n\nvalidator((Baz, Baz) as x) {\n fn baz() { todo }\n}\n\nvalidator((fst, snd) as x: Pair) {\n fn fiz() { todo }\n}\n" +--- +validator(Foo { a, b, .. }) { + fn foo() { + todo + } +} + +validator([Bar]: List) { + fn bar() { + todo + } +} + +validator((Baz, Baz) as x) { + fn baz() { + todo + } +} + +validator((fst, snd) as x: Pair) { + fn fiz() { + todo + } +} diff --git a/crates/aiken-lang/src/tipo/environment.rs b/crates/aiken-lang/src/tipo/environment.rs index a5b6300a..81753bf4 100644 --- a/crates/aiken-lang/src/tipo/environment.rs +++ b/crates/aiken-lang/src/tipo/environment.rs @@ -1149,7 +1149,7 @@ impl<'a> Environment<'a> { let mut field_map = FieldMap::new(arguments.len(), true); for (i, arg) in arguments.iter().enumerate() { - field_map.insert(arg.arg_name.get_label(), i, &arg.location)?; + field_map.insert(arg.arg_name(i).get_label(), i, &arg.location)?; } let field_map = field_map.into_option(); diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index f0b22562..036a166f 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -10,7 +10,7 @@ use super::{ }; use crate::{ ast::{ - self, Annotation, Arg, ArgName, AssignmentKind, AssignmentPattern, BinOp, Bls12_381Point, + self, Annotation, ArgName, AssignmentKind, AssignmentPattern, BinOp, Bls12_381Point, ByteArrayFormatPreference, CallArg, ClauseGuard, Constant, Curve, Function, IfBranch, LogicalOpChainKind, Pattern, RecordUpdateSpread, Span, TraceKind, TraceLevel, Tracing, TypedArg, TypedCallArg, TypedClause, TypedClauseGuard, TypedIfBranch, TypedPattern, @@ -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"); @@ -79,7 +112,8 @@ pub(crate) fn infer_function( let arguments = arguments .iter() .zip(&args_types) - .map(|(arg_name, tipo)| arg_name.to_owned().set_type(tipo.clone())) + .enumerate() + .map(|(ix, (arg_name, tipo))| arg_name.to_owned().set_type(tipo.clone(), ix)) .collect(); let hydrator = hydrators @@ -330,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())?; - + 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); } @@ -341,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) } @@ -1072,16 +1132,19 @@ impl<'a, 'b> ExprTyper<'a, 'b> { fn infer_param( &mut self, - arg: UntypedArg, + untyped_arg: UntypedArg, expected: Option>, - ) -> Result { - let Arg { - arg_name, + ix: usize, + ) -> Result<(TypedArg, Option), Error> { + let arg_name = untyped_arg.arg_name(ix); + + let UntypedArg { + by, annotation, location, doc, - tipo: _, - } = arg; + is_validator_param, + } = untyped_arg; let tipo = annotation .clone() @@ -1097,13 +1160,18 @@ impl<'a, 'b> ExprTyper<'a, 'b> { self.unify(expected, tipo.clone(), location, false)?; } - Ok(Arg { + 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( @@ -1733,12 +1801,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { for arg in &args { match &arg.arg_name { - ArgName::Named { - name, - is_validator_param, - location, - .. - } if !is_validator_param => { + ArgName::Named { name, location, .. } if !arg.is_validator_param => { if let Some(duplicate_location) = argument_names.insert(name, location) { return Err(Error::DuplicateArgument { location: *location, @@ -1965,7 +2028,6 @@ impl<'a, 'b> ExprTyper<'a, 'b> { label: name.clone(), name, location: var_location, - is_validator_param: false, }; names.push((name, assignment_pattern_location, annotation)); @@ -1989,7 +2051,6 @@ impl<'a, 'b> ExprTyper<'a, 'b> { label: name.clone(), name: name.clone(), location: pattern.location(), - is_validator_param: false, }; let pattern_is_var = pattern.is_var(); diff --git a/crates/aiken-lang/src/tipo/infer.rs b/crates/aiken-lang/src/tipo/infer.rs index f4f21e85..075aa774 100644 --- a/crates/aiken-lang/src/tipo/infer.rs +++ b/crates/aiken-lang/src/tipo/infer.rs @@ -7,8 +7,8 @@ use super::{ }; use crate::{ ast::{ - Annotation, Arg, ArgName, ArgVia, DataType, Definition, Function, ModuleConstant, - ModuleKind, RecordConstructor, RecordConstructorArg, Tracing, TypeAlias, TypedDefinition, + Annotation, ArgName, ArgVia, DataType, Definition, Function, ModuleConstant, ModuleKind, + RecordConstructor, RecordConstructorArg, Tracing, TypeAlias, TypedArg, TypedDefinition, TypedFunction, TypedModule, UntypedDefinition, UntypedModule, Use, Validator, }, builtins, @@ -198,14 +198,17 @@ fn infer_definition( .function_types() .expect("Preregistered type for fn was not a fn"); - for (arg, t) in params.iter().zip(args_types[0..params.len()].iter()) { - match &arg.arg_name { + for (ix, (arg, t)) in params + .iter() + .zip(args_types[0..params.len()].iter()) + .enumerate() + { + match &arg.arg_name(ix) { ArgName::Named { name, - is_validator_param, label: _, location: _, - } if *is_validator_param => { + } if arg.is_validator_param => { environment.insert_variable( name.to_string(), ValueConstructorVariant::LocalVariable { @@ -326,7 +329,12 @@ fn infer_definition( if f.arguments.len() > 1 { return Err(Error::IncorrectTestArity { count: f.arguments.len(), - location: f.arguments.get(1).expect("arguments.len() > 1").location, + location: f + .arguments + .get(1) + .expect("arguments.len() > 1") + .arg + .location, }); } @@ -336,6 +344,7 @@ fn infer_definition( let hydrator: &mut Hydrator = hydrators.get_mut(&f.name).unwrap(); let provided_inner_type = arg + .arg .annotation .as_ref() .map(|ann| hydrator.type_from_annotation(ann, environment)) @@ -352,13 +361,14 @@ fn infer_definition( // Fuzzer. if let Some(provided_inner_type) = provided_inner_type { if !arg + .arg .annotation .as_ref() .unwrap() .is_logically_equal(&inferred_annotation) { return Err(Error::CouldNotUnify { - location: arg.location, + location: arg.arg.location, expected: inferred_inner_type.clone(), given: provided_inner_type.clone(), situation: Some(UnifyErrorSituation::FuzzerAnnotationMismatch), @@ -417,23 +427,17 @@ fn infer_definition( public: typed_f.public, arguments: match typed_via { Some((via, tipo)) => { - let Arg { - arg_name, - location, - annotation: _, - doc: _, - tipo: _, - } = typed_f + let arg = typed_f .arguments .first() .expect("has exactly one argument") .to_owned(); - vec![ArgVia { - annotation, - arg_name, - location, - tipo, + arg: TypedArg { + tipo, + annotation, + ..arg + }, via, }] } diff --git a/crates/aiken-project/src/test_framework.rs b/crates/aiken-project/src/test_framework.rs index 305c76c4..9c7b3e80 100644 --- a/crates/aiken-project/src/test_framework.rs +++ b/crates/aiken-project/src/test_framework.rs @@ -1,6 +1,6 @@ use aiken_lang::ast::OnTestFailure; pub(crate) use aiken_lang::{ - ast::{Arg, BinOp, DataTypeKey, IfBranch, Span, TypedDataType, TypedTest}, + ast::{BinOp, DataTypeKey, IfBranch, Span, TypedArg, TypedDataType, TypedTest}, builtins::bool, expr::{TypedExpr, UntypedExpr}, format::Formatter, @@ -124,13 +124,13 @@ impl Test { let via = parameter.via.clone(); - let type_info = parameter.tipo.clone(); + let type_info = parameter.arg.tipo.clone(); let stripped_type_info = convert_opaque_type(&type_info, generator.data_types(), true); let program = generator.clone().generate_raw( &test.body, - &[Arg { + &[TypedArg { tipo: stripped_type_info.clone(), ..parameter.clone().into() }], diff --git a/examples/acceptance_tests/104/aiken.lock b/examples/acceptance_tests/104/aiken.lock new file mode 100644 index 00000000..7319f59c --- /dev/null +++ b/examples/acceptance_tests/104/aiken.lock @@ -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"] diff --git a/examples/acceptance_tests/104/aiken.toml b/examples/acceptance_tests/104/aiken.toml new file mode 100644 index 00000000..9144ac68 --- /dev/null +++ b/examples/acceptance_tests/104/aiken.toml @@ -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" diff --git a/examples/acceptance_tests/104/plutus.json b/examples/acceptance_tests/104/plutus.json new file mode 100644 index 00000000..e2c093d5 --- /dev/null +++ b/examples/acceptance_tests/104/plutus.json @@ -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" + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/examples/acceptance_tests/104/validators/tests.ak b/examples/acceptance_tests/104/validators/tests.ak new file mode 100644 index 00000000..f8f3582e --- /dev/null +++ b/examples/acceptance_tests/104/validators/tests.ak @@ -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 +}