From 4d42c6cb19f8018eab2d98b14710f73cae3e852c Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 7 Jun 2024 11:17:16 +0200 Subject: [PATCH 1/3] Introduce 'ArgBy' to allow defining function arg not only by name. --- crates/aiken-lang/src/ast.rs | 123 +++-- crates/aiken-lang/src/builtins.rs | 18 +- crates/aiken-lang/src/expr.rs | 23 +- crates/aiken-lang/src/format.rs | 48 +- .../src/parser/definition/function.rs | 6 +- .../snapshots/def_invalid_property_test.snap | 36 +- .../snapshots/def_property_test.snap | 18 +- .../def_property_test_annotated_fuzzer.snap | 34 +- .../snapshots/double_validator.snap | 85 ++-- .../definition/snapshots/validator.snap | 51 +- .../aiken-lang/src/parser/definition/test.rs | 10 +- .../src/parser/expr/anonymous_binop.rs | 17 +- .../src/parser/expr/anonymous_function.rs | 8 +- .../snapshots/anonymous_function_basic.snap | 17 +- .../anonymous_function_body_sequence.snap | 77 +++ .../expr/snapshots/first_class_binop.snap | 442 +++++++++--------- .../expr/snapshots/function_invoke.snap | 34 +- crates/aiken-lang/src/tipo/environment.rs | 2 +- crates/aiken-lang/src/tipo/expr.rs | 22 +- crates/aiken-lang/src/tipo/infer.rs | 41 +- crates/aiken-project/src/test_framework.rs | 27 +- 21 files changed, 651 insertions(+), 488 deletions(-) create mode 100644 crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_body_sequence.snap diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index fecbef99..1e03e402 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}, @@ -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,28 +791,64 @@ 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), +} #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct Arg { - pub arg_name: ArgName, +pub struct UntypedArg { + pub by: ArgBy, pub location: Span, pub annotation: Option, pub doc: Option, - pub tipo: T, } -impl Arg { - pub fn set_type(self, tipo: B) -> Arg { - Arg { +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, + // TODO: This should likely be moved up inside 'UntypedArg'. + is_validator_param: false, + } + } + } + } + + pub fn set_type(self, tipo: Rc, ix: usize) -> TypedArg { + TypedArg { tipo, - arg_name: self.arg_name, + arg_name: self.arg_name(ix), location: self.location, annotation: self.annotation, 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: Rc, +} + +impl TypedArg { + pub fn put_doc(&mut self, new_doc: String) { + self.doc = Some(new_doc); + } pub fn get_variable_name(&self) -> Option<&str> { self.arg_name.get_variable_name() @@ -830,12 +867,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 +878,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)) }) diff --git a/crates/aiken-lang/src/builtins.rs b/crates/aiken-lang/src/builtins.rs index d5131909..4a2b379e 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,7 +932,7 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap IndexMap 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: (), + }), }); ast::CallArg { @@ -1358,12 +1358,11 @@ 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, + 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..7350a949 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,41 +459,39 @@ 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)), - } - .group(); + 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 doc = doc_comments.append(doc.group()).group(); 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 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!(), } .append(" via ") - .append(self.expr(&arg.via, false)) + .append(self.expr(&arg_via.via, false)) .group(); let doc = doc_comments.append(doc.group()).group(); @@ -1983,11 +1981,7 @@ 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 { diff --git a/crates/aiken-lang/src/parser/definition/function.rs b/crates/aiken-lang/src/parser/definition/function.rs index bfd68006..555fd925 100644 --- a/crates/aiken-lang/src/parser/definition/function.rs +++ b/crates/aiken-lang/src/parser/definition/function.rs @@ -1,5 +1,6 @@ use crate::{ ast, + ast::ArgBy::ByName, expr::UntypedExpr, parser::{annotation, error::ParseError, expr, token::Token, utils}, }; @@ -78,12 +79,11 @@ pub fn param(is_validator_param: bool) -> impl Parser impl Parser { .then_ignore(just(Token::Via)) .then(fuzzer()) .map(|((arg_name, annotation, location), via)| ast::ArgVia { - arg_name, + arg: ast::UntypedArg { + by: ast::ArgBy::ByName(arg_name), + annotation, + location, + doc: None, + }, 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..146e1cba 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, - }, + }), 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, - }, + }), 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..1963a584 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}, }; +use chumsky::prelude::*; pub fn parser( sequence: Recursive<'_, Token, UntypedExpr, ParseError>, @@ -47,12 +46,11 @@ pub fn params() -> impl Parser { }), )) .then(just(Token::Colon).ignore_then(annotation()).or_not()) - .map_with_span(|(arg_name, annotation), span| ast::Arg { + .map_with_span(|(arg_name, annotation), span| ast::UntypedArg { location: span, annotation, doc: None, - tipo: (), - arg_name, + by: ast::ArgBy::ByName(arg_name), }) } 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..95e04ce0 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,15 @@ 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, + is_validator_param: false, + }, + ), location: 4..10, annotation: Some( Constructor { @@ -23,7 +25,6 @@ Fn { }, ), doc: None, - tipo: (), }, ], 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/first_class_binop.snap b/crates/aiken-lang/src/parser/expr/snapshots/first_class_binop.snap index 3a021757..fb216f72 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,15 @@ 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, + is_validator_param: false, + }, + ), location: 16..17, annotation: Some( Constructor { @@ -41,15 +43,16 @@ Sequence { }, ), doc: None, - tipo: (), }, - 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, + is_validator_param: false, + }, + ), location: 16..17, annotation: Some( Constructor { @@ -60,7 +63,6 @@ Sequence { }, ), doc: None, - tipo: (), }, ], body: BinOp { @@ -119,13 +121,15 @@ 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, + is_validator_param: false, + }, + ), location: 38..40, annotation: Some( Constructor { @@ -136,15 +140,16 @@ Sequence { }, ), doc: None, - tipo: (), }, - 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, + is_validator_param: false, + }, + ), location: 38..40, annotation: Some( Constructor { @@ -155,7 +160,6 @@ Sequence { }, ), doc: None, - tipo: (), }, ], body: BinOp { @@ -214,13 +218,15 @@ 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, + is_validator_param: false, + }, + ), location: 61..62, annotation: Some( Constructor { @@ -231,15 +237,16 @@ Sequence { }, ), doc: None, - tipo: (), }, - 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, + is_validator_param: false, + }, + ), location: 61..62, annotation: Some( Constructor { @@ -250,7 +257,6 @@ Sequence { }, ), doc: None, - tipo: (), }, ], body: BinOp { @@ -309,13 +315,15 @@ 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, + is_validator_param: false, + }, + ), location: 83..85, annotation: Some( Constructor { @@ -326,15 +334,16 @@ Sequence { }, ), doc: None, - tipo: (), }, - 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, + is_validator_param: false, + }, + ), location: 83..85, annotation: Some( Constructor { @@ -345,7 +354,6 @@ Sequence { }, ), doc: None, - tipo: (), }, ], 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, + is_validator_param: false, + }, + ), location: 106..108, annotation: None, doc: None, - tipo: (), }, - 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, + is_validator_param: false, + }, + ), location: 106..108, annotation: None, doc: None, - tipo: (), }, ], 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, + is_validator_param: false, + }, + ), location: 129..131, annotation: None, doc: None, - tipo: (), }, - 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, + is_validator_param: false, + }, + ), location: 129..131, annotation: None, doc: None, - tipo: (), }, ], body: BinOp { @@ -566,13 +578,15 @@ 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, + is_validator_param: false, + }, + ), location: 152..154, annotation: Some( Constructor { @@ -583,15 +597,16 @@ Sequence { }, ), doc: None, - tipo: (), }, - 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, + is_validator_param: false, + }, + ), location: 152..154, annotation: Some( Constructor { @@ -602,7 +617,6 @@ Sequence { }, ), doc: None, - tipo: (), }, ], body: BinOp { @@ -661,13 +675,15 @@ 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, + is_validator_param: false, + }, + ), location: 175..177, annotation: Some( Constructor { @@ -678,15 +694,16 @@ Sequence { }, ), doc: None, - tipo: (), }, - 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, + is_validator_param: false, + }, + ), location: 175..177, annotation: Some( Constructor { @@ -697,7 +714,6 @@ Sequence { }, ), doc: None, - tipo: (), }, ], body: BinOp { @@ -756,13 +772,15 @@ 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, + is_validator_param: false, + }, + ), location: 198..199, annotation: Some( Constructor { @@ -773,15 +791,16 @@ Sequence { }, ), doc: None, - tipo: (), }, - 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, + is_validator_param: false, + }, + ), location: 198..199, annotation: Some( Constructor { @@ -792,7 +811,6 @@ Sequence { }, ), doc: None, - tipo: (), }, ], body: BinOp { @@ -851,13 +869,15 @@ 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, + is_validator_param: false, + }, + ), location: 220..221, annotation: Some( Constructor { @@ -868,15 +888,16 @@ Sequence { }, ), doc: None, - tipo: (), }, - 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, + is_validator_param: false, + }, + ), location: 220..221, annotation: Some( Constructor { @@ -887,7 +908,6 @@ Sequence { }, ), doc: None, - tipo: (), }, ], body: BinOp { @@ -946,13 +966,15 @@ 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, + is_validator_param: false, + }, + ), location: 242..243, annotation: Some( Constructor { @@ -963,15 +985,16 @@ Sequence { }, ), doc: None, - tipo: (), }, - 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, + is_validator_param: false, + }, + ), location: 242..243, annotation: Some( Constructor { @@ -982,7 +1005,6 @@ Sequence { }, ), doc: None, - tipo: (), }, ], body: BinOp { @@ -1041,13 +1063,15 @@ 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, + is_validator_param: false, + }, + ), location: 264..265, annotation: Some( Constructor { @@ -1058,15 +1082,16 @@ Sequence { }, ), doc: None, - tipo: (), }, - 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, + is_validator_param: false, + }, + ), location: 264..265, annotation: Some( Constructor { @@ -1077,7 +1102,6 @@ Sequence { }, ), doc: None, - tipo: (), }, ], body: BinOp { @@ -1136,13 +1160,15 @@ 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, + is_validator_param: false, + }, + ), location: 286..287, annotation: Some( Constructor { @@ -1153,15 +1179,16 @@ Sequence { }, ), doc: None, - tipo: (), }, - 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, + is_validator_param: false, + }, + ), location: 286..287, annotation: Some( Constructor { @@ -1172,7 +1199,6 @@ Sequence { }, ), doc: None, - tipo: (), }, ], 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..77ab2d30 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, + is_validator_param: false, + }, + ), location: 0..0, annotation: None, doc: None, - tipo: (), }, ], 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, + is_validator_param: false, + }, + ), location: 52..53, annotation: None, doc: None, - tipo: (), }, ], body: BinOp { 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..920022d3 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, @@ -79,7 +79,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 @@ -331,7 +332,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { let mut arguments = Vec::new(); for (i, arg) in args.into_iter().enumerate() { - let arg = self.infer_param(arg, expected_args.get(i).cloned())?; + let arg = self.infer_param(arg, expected_args.get(i).cloned(), i)?; arguments.push(arg); } @@ -1070,18 +1071,21 @@ impl<'a, 'b> ExprTyper<'a, 'b> { }) } + // TODO: Handle arg pattern fn infer_param( &mut self, - arg: UntypedArg, + untyped_arg: UntypedArg, expected: Option>, + ix: usize, ) -> Result { - let Arg { - arg_name, + let arg_name = untyped_arg.arg_name(ix); + + let UntypedArg { + by, annotation, location, doc, - tipo: _, - } = arg; + } = untyped_arg; let tipo = annotation .clone() @@ -1097,7 +1101,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { self.unify(expected, tipo.clone(), location, false)?; } - Ok(Arg { + Ok(TypedArg { arg_name, location, annotation, diff --git a/crates/aiken-lang/src/tipo/infer.rs b/crates/aiken-lang/src/tipo/infer.rs index f4f21e85..0309e148 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,8 +198,12 @@ 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, @@ -326,7 +330,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 +345,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 +362,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 +428,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..d42a39c0 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() }], @@ -507,8 +507,10 @@ 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(), @@ -1087,9 +1089,11 @@ impl TryFrom for Assertion { final_else, .. } => { - if let [IfBranch { - condition, body, .. - }] = &branches[..] + if let [ + IfBranch { + condition, body, .. + }, + ] = &branches[..] { let then_is_true = match body { TypedExpr::Var { @@ -1508,13 +1512,14 @@ mod test { } "#}); - assert!(prop - .run::<()>( + assert!( + prop.run::<()>( 42, PropertyTest::DEFAULT_MAX_SUCCESS, &PlutusVersion::default() ) - .is_success()); + .is_success() + ); } #[test] From b6da42baf2b9291cb341f865c348233f4edd6018 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 7 Jun 2024 11:32:05 +0200 Subject: [PATCH 2/3] Bump 'is_validator_param' up from 'ArgName' to '...Arg' There's no reasons for this to be a property of only ArgName::Named to begin with. And now, with the extra indirection introduced for arg_name, it may leads to subtle issues when patterns args are used in validators. --- crates/aiken-lang/src/ast.rs | 6 +-- crates/aiken-lang/src/builtins.rs | 13 ++--- crates/aiken-lang/src/expr.rs | 3 +- .../src/parser/definition/function.rs | 9 ++-- .../snapshots/def_invalid_property_test.snap | 4 +- .../snapshots/def_property_test.snap | 2 +- .../def_property_test_annotated_fuzzer.snap | 2 +- .../snapshots/double_validator.snap | 10 ++-- .../definition/snapshots/validator.snap | 6 +-- .../aiken-lang/src/parser/definition/test.rs | 12 ++--- .../src/parser/expr/anonymous_binop.rs | 4 +- .../src/parser/expr/anonymous_function.rs | 2 +- .../snapshots/anonymous_function_basic.snap | 2 +- .../expr/snapshots/first_class_binop.snap | 52 +++++++++---------- .../expr/snapshots/function_invoke.snap | 4 +- crates/aiken-lang/src/tipo/expr.rs | 11 ++-- crates/aiken-lang/src/tipo/infer.rs | 3 +- 17 files changed, 69 insertions(+), 76 deletions(-) diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 1e03e402..d83211c1 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -803,6 +803,7 @@ pub struct UntypedArg { pub location: Span, pub annotation: Option, pub doc: Option, + pub is_validator_param: bool, } impl UntypedArg { @@ -818,8 +819,6 @@ impl UntypedArg { label: name.clone(), name, location: self.location, - // TODO: This should likely be moved up inside 'UntypedArg'. - is_validator_param: false, } } } @@ -831,6 +830,7 @@ impl UntypedArg { arg_name: self.arg_name(ix), location: self.location, annotation: self.annotation, + is_validator_param: self.is_validator_param, doc: self.doc, } } @@ -842,6 +842,7 @@ pub struct TypedArg { pub location: Span, pub annotation: Option, pub doc: Option, + pub is_validator_param: bool, pub tipo: Rc, } @@ -928,7 +929,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 4a2b379e..0975965c 100644 --- a/crates/aiken-lang/src/builtins.rs +++ b/crates/aiken-lang/src/builtins.rs @@ -937,8 +937,8 @@ pub fn prelude_functions(id_gen: &IdGenerator) -> IndexMap IndexMap IndexMap IndexMap IndexMap IndexMap IndexMap impl Parser name} .then(select! {Token::Name {name} => name}) - .map_with_span(move |(label, name), span| ast::ArgName::Named { + .map_with_span(|(label, name), span| ast::ArgName::Named { label, name, location: span, - is_validator_param, }), - select! {Token::Name {name} => name}.map_with_span(move |name, span| ast::ArgName::Named { + select! {Token::Name {name} => name}.map_with_span(|name, span| ast::ArgName::Named { label: name.clone(), name, location: span, - is_validator_param, }), )) .then(just(Token::Colon).ignore_then(annotation()).or_not()) - .map_with_span(|(arg_name, annotation), span| ast::UntypedArg { + .map_with_span(move |(arg_name, annotation), span| ast::UntypedArg { location: span, annotation, doc: None, + is_validator_param, by: ByName(arg_name), }) } 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 f6416221..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 @@ -12,12 +12,12 @@ Test( name: "x", label: "x", location: 9..10, - is_validator_param: false, }, ), location: 9..10, annotation: None, doc: None, + is_validator_param: false, }, via: Var { location: 15..16, @@ -31,12 +31,12 @@ Test( name: "y", label: "y", location: 18..19, - is_validator_param: false, }, ), location: 18..19, annotation: None, doc: None, + is_validator_param: false, }, via: Var { location: 24..25, 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 67658145..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 @@ -12,12 +12,12 @@ Test( name: "x", label: "x", location: 9..10, - is_validator_param: false, }, ), location: 9..10, annotation: None, doc: None, + is_validator_param: false, }, via: FieldAccess { location: 15..27, 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 e9f10a97..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 @@ -12,7 +12,6 @@ Test( name: "x", label: "x", location: 9..10, - is_validator_param: false, }, ), location: 9..15, @@ -25,6 +24,7 @@ Test( }, ), doc: None, + is_validator_param: false, }, via: Call { arguments: [], 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 d577d571..5235bbdb 100644 --- a/crates/aiken-lang/src/parser/definition/snapshots/double_validator.snap +++ b/crates/aiken-lang/src/parser/definition/snapshots/double_validator.snap @@ -14,12 +14,12 @@ Validator( name: "datum", label: "datum", location: 21..26, - is_validator_param: false, }, ), location: 21..26, annotation: None, doc: None, + is_validator_param: false, }, UntypedArg { by: ByName( @@ -27,12 +27,12 @@ Validator( name: "rdmr", label: "rdmr", location: 28..32, - is_validator_param: false, }, ), location: 28..32, annotation: None, doc: None, + is_validator_param: false, }, UntypedArg { by: ByName( @@ -40,12 +40,12 @@ Validator( name: "ctx", label: "ctx", location: 34..37, - is_validator_param: false, }, ), location: 34..37, annotation: None, doc: None, + is_validator_param: false, }, ], body: Var { @@ -70,12 +70,12 @@ Validator( name: "rdmr", label: "rdmr", location: 64..68, - is_validator_param: false, }, ), location: 64..68, annotation: None, doc: None, + is_validator_param: false, }, UntypedArg { by: ByName( @@ -83,12 +83,12 @@ Validator( name: "ctx", label: "ctx", location: 70..73, - is_validator_param: false, }, ), location: 70..73, annotation: None, doc: None, + is_validator_param: false, }, ], body: Var { diff --git a/crates/aiken-lang/src/parser/definition/snapshots/validator.snap b/crates/aiken-lang/src/parser/definition/snapshots/validator.snap index 0a91ff1d..fad9a357 100644 --- a/crates/aiken-lang/src/parser/definition/snapshots/validator.snap +++ b/crates/aiken-lang/src/parser/definition/snapshots/validator.snap @@ -14,12 +14,12 @@ Validator( name: "datum", label: "datum", location: 21..26, - is_validator_param: false, }, ), location: 21..26, annotation: None, doc: None, + is_validator_param: false, }, UntypedArg { by: ByName( @@ -27,12 +27,12 @@ Validator( name: "rdmr", label: "rdmr", location: 28..32, - is_validator_param: false, }, ), location: 28..32, annotation: None, doc: None, + is_validator_param: false, }, UntypedArg { by: ByName( @@ -40,12 +40,12 @@ Validator( name: "ctx", label: "ctx", location: 34..37, - is_validator_param: false, }, ), location: 34..37, annotation: None, doc: None, + 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 61123ca3..7e2cbec1 100644 --- a/crates/aiken-lang/src/parser/definition/test.rs +++ b/crates/aiken-lang/src/parser/definition/test.rs @@ -60,13 +60,10 @@ pub fn via() -> impl Parser { location: span, } }), - select! {Token::Name {name} => name}.map_with_span(move |name, location| { - ast::ArgName::Named { - label: name.clone(), - name, - location, - is_validator_param: false, - } + select! {Token::Name {name} => name}.map_with_span(|name, location| ast::ArgName::Named { + label: name.clone(), + name, + location, }), )) .then(just(Token::Colon).ignore_then(annotation()).or_not()) @@ -79,6 +76,7 @@ pub fn via() -> impl Parser { annotation, location, doc: None, + is_validator_param: false, }, via, }) diff --git a/crates/aiken-lang/src/parser/expr/anonymous_binop.rs b/crates/aiken-lang/src/parser/expr/anonymous_binop.rs index 146e1cba..f4835eda 100644 --- a/crates/aiken-lang/src/parser/expr/anonymous_binop.rs +++ b/crates/aiken-lang/src/parser/expr/anonymous_binop.rs @@ -45,8 +45,8 @@ pub fn parser() -> impl Parser { name: "left".to_string(), label: "left".to_string(), location, - is_validator_param: false, }), + is_validator_param: false, annotation: arg_annotation.clone(), doc: None, location, @@ -56,8 +56,8 @@ pub fn parser() -> impl Parser { name: "right".to_string(), label: "right".to_string(), location, - is_validator_param: false, }), + is_validator_param: false, annotation: arg_annotation, doc: None, location, diff --git a/crates/aiken-lang/src/parser/expr/anonymous_function.rs b/crates/aiken-lang/src/parser/expr/anonymous_function.rs index 1963a584..2d092ce4 100644 --- a/crates/aiken-lang/src/parser/expr/anonymous_function.rs +++ b/crates/aiken-lang/src/parser/expr/anonymous_function.rs @@ -42,11 +42,11 @@ pub fn params() -> impl Parser { label: name.clone(), name, location: span, - is_validator_param: false, }), )) .then(just(Token::Colon).ignore_then(annotation()).or_not()) .map_with_span(|(arg_name, annotation), span| ast::UntypedArg { + is_validator_param: false, location: span, annotation, doc: None, 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 95e04ce0..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 @@ -12,7 +12,6 @@ Fn { name: "a", label: "a", location: 4..5, - is_validator_param: false, }, ), location: 4..10, @@ -25,6 +24,7 @@ Fn { }, ), doc: None, + is_validator_param: false, }, ], body: BinOp { 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 fb216f72..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 @@ -30,7 +30,6 @@ Sequence { name: "left", label: "left", location: 16..17, - is_validator_param: false, }, ), location: 16..17, @@ -43,6 +42,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, UntypedArg { by: ByName( @@ -50,7 +50,6 @@ Sequence { name: "right", label: "right", location: 16..17, - is_validator_param: false, }, ), location: 16..17, @@ -63,6 +62,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, ], body: BinOp { @@ -127,7 +127,6 @@ Sequence { name: "left", label: "left", location: 38..40, - is_validator_param: false, }, ), location: 38..40, @@ -140,6 +139,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, UntypedArg { by: ByName( @@ -147,7 +147,6 @@ Sequence { name: "right", label: "right", location: 38..40, - is_validator_param: false, }, ), location: 38..40, @@ -160,6 +159,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, ], body: BinOp { @@ -224,7 +224,6 @@ Sequence { name: "left", label: "left", location: 61..62, - is_validator_param: false, }, ), location: 61..62, @@ -237,6 +236,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, UntypedArg { by: ByName( @@ -244,7 +244,6 @@ Sequence { name: "right", label: "right", location: 61..62, - is_validator_param: false, }, ), location: 61..62, @@ -257,6 +256,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, ], body: BinOp { @@ -321,7 +321,6 @@ Sequence { name: "left", label: "left", location: 83..85, - is_validator_param: false, }, ), location: 83..85, @@ -334,6 +333,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, UntypedArg { by: ByName( @@ -341,7 +341,6 @@ Sequence { name: "right", label: "right", location: 83..85, - is_validator_param: false, }, ), location: 83..85, @@ -354,6 +353,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, ], body: BinOp { @@ -418,12 +418,12 @@ Sequence { name: "left", label: "left", location: 106..108, - is_validator_param: false, }, ), location: 106..108, annotation: None, doc: None, + is_validator_param: false, }, UntypedArg { by: ByName( @@ -431,12 +431,12 @@ Sequence { name: "right", label: "right", location: 106..108, - is_validator_param: false, }, ), location: 106..108, annotation: None, doc: None, + is_validator_param: false, }, ], body: BinOp { @@ -501,12 +501,12 @@ Sequence { name: "left", label: "left", location: 129..131, - is_validator_param: false, }, ), location: 129..131, annotation: None, doc: None, + is_validator_param: false, }, UntypedArg { by: ByName( @@ -514,12 +514,12 @@ Sequence { name: "right", label: "right", location: 129..131, - is_validator_param: false, }, ), location: 129..131, annotation: None, doc: None, + is_validator_param: false, }, ], body: BinOp { @@ -584,7 +584,6 @@ Sequence { name: "left", label: "left", location: 152..154, - is_validator_param: false, }, ), location: 152..154, @@ -597,6 +596,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, UntypedArg { by: ByName( @@ -604,7 +604,6 @@ Sequence { name: "right", label: "right", location: 152..154, - is_validator_param: false, }, ), location: 152..154, @@ -617,6 +616,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, ], body: BinOp { @@ -681,7 +681,6 @@ Sequence { name: "left", label: "left", location: 175..177, - is_validator_param: false, }, ), location: 175..177, @@ -694,6 +693,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, UntypedArg { by: ByName( @@ -701,7 +701,6 @@ Sequence { name: "right", label: "right", location: 175..177, - is_validator_param: false, }, ), location: 175..177, @@ -714,6 +713,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, ], body: BinOp { @@ -778,7 +778,6 @@ Sequence { name: "left", label: "left", location: 198..199, - is_validator_param: false, }, ), location: 198..199, @@ -791,6 +790,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, UntypedArg { by: ByName( @@ -798,7 +798,6 @@ Sequence { name: "right", label: "right", location: 198..199, - is_validator_param: false, }, ), location: 198..199, @@ -811,6 +810,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, ], body: BinOp { @@ -875,7 +875,6 @@ Sequence { name: "left", label: "left", location: 220..221, - is_validator_param: false, }, ), location: 220..221, @@ -888,6 +887,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, UntypedArg { by: ByName( @@ -895,7 +895,6 @@ Sequence { name: "right", label: "right", location: 220..221, - is_validator_param: false, }, ), location: 220..221, @@ -908,6 +907,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, ], body: BinOp { @@ -972,7 +972,6 @@ Sequence { name: "left", label: "left", location: 242..243, - is_validator_param: false, }, ), location: 242..243, @@ -985,6 +984,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, UntypedArg { by: ByName( @@ -992,7 +992,6 @@ Sequence { name: "right", label: "right", location: 242..243, - is_validator_param: false, }, ), location: 242..243, @@ -1005,6 +1004,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, ], body: BinOp { @@ -1069,7 +1069,6 @@ Sequence { name: "left", label: "left", location: 264..265, - is_validator_param: false, }, ), location: 264..265, @@ -1082,6 +1081,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, UntypedArg { by: ByName( @@ -1089,7 +1089,6 @@ Sequence { name: "right", label: "right", location: 264..265, - is_validator_param: false, }, ), location: 264..265, @@ -1102,6 +1101,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, ], body: BinOp { @@ -1166,7 +1166,6 @@ Sequence { name: "left", label: "left", location: 286..287, - is_validator_param: false, }, ), location: 286..287, @@ -1179,6 +1178,7 @@ Sequence { }, ), doc: None, + is_validator_param: false, }, UntypedArg { by: ByName( @@ -1186,7 +1186,6 @@ Sequence { name: "right", label: "right", location: 286..287, - is_validator_param: false, }, ), location: 286..287, @@ -1199,6 +1198,7 @@ Sequence { }, ), doc: None, + 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 77ab2d30..d3dc4cec 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/function_invoke.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/function_invoke.snap @@ -53,12 +53,12 @@ Sequence { name: "_capture__0", label: "_capture__0", location: 0..0, - is_validator_param: false, }, ), location: 0..0, annotation: None, doc: None, + is_validator_param: false, }, ], body: Call { @@ -84,12 +84,12 @@ Sequence { name: "y", label: "y", location: 52..53, - is_validator_param: false, }, ), location: 52..53, annotation: None, doc: None, + is_validator_param: false, }, ], body: BinOp { diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 920022d3..3a29539a 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -1085,6 +1085,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { annotation, location, doc, + is_validator_param, } = untyped_arg; let tipo = annotation @@ -1106,6 +1107,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { location, annotation, tipo, + is_validator_param, doc, }) } @@ -1737,12 +1739,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, @@ -1969,7 +1966,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)); @@ -1993,7 +1989,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 0309e148..075aa774 100644 --- a/crates/aiken-lang/src/tipo/infer.rs +++ b/crates/aiken-lang/src/tipo/infer.rs @@ -206,10 +206,9 @@ fn infer_definition( 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 { From 858dfccc823335fc1d865b3c2d461b42dd45d30b Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 7 Jun 2024 15:23:33 +0200 Subject: [PATCH 3/3] 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. --- CHANGELOG.md | 4 + crates/aiken-lang/src/ast.rs | 28 ++++++- crates/aiken-lang/src/format.rs | 36 +++++---- .../src/parser/definition/function.rs | 72 +++++++++++++---- .../function_by_pattern_no_annotation.snap | 62 ++++++++++++++ .../function_by_pattern_with_alias.snap | 69 ++++++++++++++++ .../function_by_pattern_with_annotation.snap | 69 ++++++++++++++++ .../aiken-lang/src/parser/definition/test.rs | 20 +++-- .../src/parser/expr/anonymous_function.rs | 36 ++++++--- ...ous_function_by_pattern_no_annotation.snap | 55 +++++++++++++ ...nymous_function_by_pattern_with_alias.snap | 58 ++++++++++++++ ...s_function_by_pattern_with_annotation.snap | 62 ++++++++++++++ crates/aiken-lang/src/tests/check.rs | 34 ++++++++ crates/aiken-lang/src/tests/format.rs | 65 +++++++++++++++ .../snapshots/format_anon_fn_pattern.snap | 23 ++++++ .../tests/snapshots/format_fn_pattern.snap | 23 ++++++ .../snapshots/format_validator_pattern.snap | 27 +++++++ crates/aiken-lang/src/tipo/expr.rs | 74 +++++++++++++++-- crates/aiken-project/src/test_framework.rs | 21 ++--- examples/acceptance_tests/104/aiken.lock | 28 +++++++ examples/acceptance_tests/104/aiken.toml | 21 +++++ examples/acceptance_tests/104/plutus.json | 80 +++++++++++++++++++ .../acceptance_tests/104/validators/tests.ak | 45 +++++++++++ 23 files changed, 944 insertions(+), 68 deletions(-) create mode 100644 crates/aiken-lang/src/parser/definition/snapshots/function_by_pattern_no_annotation.snap create mode 100644 crates/aiken-lang/src/parser/definition/snapshots/function_by_pattern_with_alias.snap create mode 100644 crates/aiken-lang/src/parser/definition/snapshots/function_by_pattern_with_annotation.snap create mode 100644 crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_by_pattern_no_annotation.snap create mode 100644 crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_by_pattern_with_alias.snap create mode 100644 crates/aiken-lang/src/parser/expr/snapshots/anonymous_function_by_pattern_with_annotation.snap create mode 100644 crates/aiken-lang/src/tests/snapshots/format_anon_fn_pattern.snap create mode 100644 crates/aiken-lang/src/tests/snapshots/format_fn_pattern.snap create mode 100644 crates/aiken-lang/src/tests/snapshots/format_validator_pattern.snap create mode 100644 examples/acceptance_tests/104/aiken.lock create mode 100644 examples/acceptance_tests/104/aiken.toml create mode 100644 examples/acceptance_tests/104/plutus.json create mode 100644 examples/acceptance_tests/104/validators/tests.ak 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 d83211c1..a3220a1e 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -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 { + 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, diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index 7350a949..c5a36dfc 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -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 { diff --git a/crates/aiken-lang/src/parser/definition/function.rs b/crates/aiken-lang/src/parser/definition/function.rs index 099f14e1..fe5f15ae 100644 --- a/crates/aiken-lang/src/parser/definition/function.rs +++ b/crates/aiken-lang/src/parser/definition/function.rs @@ -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 name} .then(select! {Token::DiscardName {name} => name}) - .map_with_span(|(label, name), span| ast::ArgName::Discarded { - label, - name, - location: span, + .map_with_span(|(label, name), span| { + ast::ArgBy::ByName(ast::ArgName::Discarded { + label, + name, + location: span, + }) }), select! {Token::DiscardName {name} => name}.map_with_span(|name, span| { - ast::ArgName::Discarded { + ast::ArgBy::ByName(ast::ArgName::Discarded { label: name.clone(), name, location: span, - } + }) }), select! {Token::Name {name} => name} .then(select! {Token::Name {name} => name}) - .map_with_span(|(label, name), span| ast::ArgName::Named { - label, + .map_with_span(|(label, name), span| { + ast::ArgBy::ByName(ast::ArgName::Named { + label, + name, + location: span, + }) + }), + select! {Token::Name {name} => name}.map_with_span(|name, span| { + ast::ArgBy::ByName(ast::ArgName::Named { + label: name.clone(), name, location: span, - }), - select! {Token::Name {name} => name}.map_with_span(|name, span| ast::ArgName::Named { - label: name.clone(), - name, - location: span, + }) }), + pattern().map(ast::ArgBy::ByPattern), )) .then(just(Token::Colon).ignore_then(annotation()).or_not()) - .map_with_span(move |(arg_name, annotation), span| ast::UntypedArg { + .map_with_span(move |(by, annotation), span| ast::UntypedArg { location: span, annotation, doc: None, is_validator_param, - by: ByName(arg_name), + by, }) } @@ -118,4 +124,36 @@ mod tests { "# ); } + + #[test] + fn function_by_pattern_no_annotation() { + assert_definition!( + r#" + fn foo(Foo { my_field }) { + my_field * 2 + } + "# + ); + } + + #[test] + fn function_by_pattern_with_annotation() { + assert_definition!( + r#" + fn foo(Foo { my_field }: Foo) { + my_field * 2 + } + "# + ); + } + #[test] + fn function_by_pattern_with_alias() { + assert_definition!( + r#" + fn foo(Foo { my_field, .. } as x) { + my_field * x.my_other_field + } + "# + ); + } } 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/test.rs b/crates/aiken-lang/src/parser/definition/test.rs index 7e2cbec1..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,25 +55,28 @@ 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(|name, location| ast::ArgName::Named { - label: name.clone(), - name, - location, + select! {Token::Name {name} => name}.map_with_span(|name, location| { + ast::ArgBy::ByName(ast::ArgName::Named { + label: name.clone(), + name, + location, + }) }), + pattern().map(ast::ArgBy::ByPattern), )) .then(just(Token::Colon).ignore_then(annotation()).or_not()) .map_with_span(|(arg_name, annotation), location| (arg_name, annotation, location)) .then_ignore(just(Token::Via)) .then(fuzzer()) - .map(|((arg_name, annotation, location), via)| ast::ArgVia { + .map(|((by, annotation, location), via)| ast::ArgVia { arg: ast::UntypedArg { - by: ast::ArgBy::ByName(arg_name), + by, annotation, location, doc: None, diff --git a/crates/aiken-lang/src/parser/expr/anonymous_function.rs b/crates/aiken-lang/src/parser/expr/anonymous_function.rs index 2d092ce4..39f35259 100644 --- a/crates/aiken-lang/src/parser/expr/anonymous_function.rs +++ b/crates/aiken-lang/src/parser/expr/anonymous_function.rs @@ -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 { // TODO: return a better error when a label is provided `UnexpectedLabel` choice(( select! {Token::DiscardName {name} => name}.map_with_span(|name, span| { - ast::ArgName::Discarded { + ast::ArgBy::ByName(ast::ArgName::Discarded { label: name.clone(), name, location: span, - } + }) }), - select! {Token::Name {name} => name}.map_with_span(|name, span| ast::ArgName::Named { - label: name.clone(), - name, - location: span, + select! {Token::Name {name} => name}.map_with_span(|name, span| { + ast::ArgBy::ByName(ast::ArgName::Named { + label: name.clone(), + name, + location: span, + }) }), + pattern().map(ast::ArgBy::ByPattern), )) .then(just(Token::Colon).ignore_then(annotation()).or_not()) - .map_with_span(|(arg_name, annotation), span| ast::UntypedArg { + .map_with_span(|(by, annotation), span| ast::UntypedArg { is_validator_param: false, location: span, annotation, doc: None, - by: ast::ArgBy::ByName(arg_name), + by, }) } @@ -62,4 +65,19 @@ mod tests { fn anonymous_function_basic() { assert_expr!(r#"fn (a: Int) -> Int { a + 1 }"#); } + + #[test] + fn anonymous_function_by_pattern_no_annotation() { + assert_expr!(r#"fn (Foo { my_field }) { my_field * 2 }"#); + } + + #[test] + fn anonymous_function_by_pattern_with_annotation() { + assert_expr!(r#"fn (Foo { my_field } : Foo) { my_field * 2 }"#); + } + + #[test] + fn anonymous_function_by_pattern_with_alias() { + assert_expr!(r#"fn (Foo { my_field, .. } as x) { my_field * my_other_field }"#); + } } 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/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/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 3a29539a..036a166f 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -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>, ix: usize, - ) -> Result { + ) -> Result<(TypedArg, Option), 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( diff --git a/crates/aiken-project/src/test_framework.rs b/crates/aiken-project/src/test_framework.rs index d42a39c0..9c7b3e80 100644 --- a/crates/aiken-project/src/test_framework.rs +++ b/crates/aiken-project/src/test_framework.rs @@ -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 for Assertion { final_else, .. } => { - if let [ - IfBranch { - condition, body, .. - }, - ] = &branches[..] + if let [IfBranch { + condition, body, .. + }] = &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] 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 +}