diff --git a/CHANGELOG.md b/CHANGELOG.md index e7a73ed2..e507b2e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - **aiken-lang**: Strings can contain a nul byte using the escape sequence `\0`. @KtorZ - **aiken**: The `check` command now accept an extra (optional) option `--max-success` to control the number of property-test iterations to perform. @KtorZ - **aiken**: The `docs` command now accept an optional flag `--include-dependencies` to include all dependencies in the generated documentation. @KtorZ +- **aiken-lang**: Implement [function backpassing](https://www.roc-lang.org/tutorial#backpassing) as a syntactic sugar. @KtorZ ### Fixed diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 203a242d..1760ec53 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -16,6 +16,7 @@ use std::{ use uplc::machine::runtime::Compressable; use vec1::Vec1; +pub const BACKPASS_VARIABLE: &str = "_backpass"; pub const CAPTURE_VARIABLE: &str = "_capture"; pub const PIPE_VARIABLE: &str = "_pipe"; @@ -792,6 +793,19 @@ impl Arg { self.arg_name.get_variable_name() } + pub fn is_capture(&self) -> bool { + if let ArgName::Named { + ref name, location, .. + } = self.arg_name + { + return name.starts_with(CAPTURE_VARIABLE) + && location == Span::empty() + && self.location == Span::empty(); + } + + false + } + pub fn put_doc(&mut self, new_doc: String) { self.doc = Some(new_doc); } @@ -1422,25 +1436,59 @@ impl Default for Bls12_381Point { } } +pub type UntypedAssignmentKind = AssignmentKind; +pub type TypedAssignmentKind = AssignmentKind<()>; + #[derive(Debug, Clone, PartialEq, Eq, Copy, serde::Serialize, serde::Deserialize)] -pub enum AssignmentKind { - Let, - Expect, +pub enum AssignmentKind { + Let { backpassing: T }, + Expect { backpassing: T }, } -impl AssignmentKind { +impl From for TypedAssignmentKind { + fn from(kind: UntypedAssignmentKind) -> TypedAssignmentKind { + match kind { + AssignmentKind::Let { .. } => AssignmentKind::Let { backpassing: () }, + AssignmentKind::Expect { .. } => AssignmentKind::Expect { backpassing: () }, + } + } +} + +impl AssignmentKind { pub fn is_let(&self) -> bool { - matches!(self, AssignmentKind::Let) + matches!(self, AssignmentKind::Let { .. }) } pub fn is_expect(&self) -> bool { - matches!(self, AssignmentKind::Expect) + matches!(self, AssignmentKind::Expect { .. }) } pub fn location_offset(&self) -> usize { match self { - AssignmentKind::Let => 3, - AssignmentKind::Expect => 6, + AssignmentKind::Let { .. } => 3, + AssignmentKind::Expect { .. } => 6, + } + } +} + +impl AssignmentKind { + pub fn is_backpassing(&self) -> bool { + match self { + Self::Let { backpassing } | Self::Expect { backpassing } => *backpassing, + } + } +} + +impl AssignmentKind { + pub fn let_() -> Self { + AssignmentKind::Let { + backpassing: Default::default(), + } + } + + pub fn expect() -> Self { + AssignmentKind::Expect { + backpassing: Default::default(), } } } diff --git a/crates/aiken-lang/src/expr.rs b/crates/aiken-lang/src/expr.rs index 28db5a8a..815bcfa3 100644 --- a/crates/aiken-lang/src/expr.rs +++ b/crates/aiken-lang/src/expr.rs @@ -1,10 +1,10 @@ use crate::{ ast::{ - self, Annotation, Arg, AssignmentKind, BinOp, Bls12_381Point, ByteArrayFormatPreference, - CallArg, Curve, DataType, DataTypeKey, DefinitionLocation, IfBranch, Located, - LogicalOpChainKind, ParsedCallArg, Pattern, RecordConstructorArg, RecordUpdateSpread, Span, - TraceKind, TypedClause, TypedDataType, TypedRecordUpdateArg, UnOp, UntypedClause, - UntypedRecordUpdateArg, + self, Annotation, Arg, ArgName, BinOp, Bls12_381Point, ByteArrayFormatPreference, CallArg, + Curve, DataType, DataTypeKey, DefinitionLocation, IfBranch, Located, LogicalOpChainKind, + ParsedCallArg, Pattern, RecordConstructorArg, RecordUpdateSpread, Span, TraceKind, + TypedAssignmentKind, TypedClause, TypedDataType, TypedRecordUpdateArg, UnOp, + UntypedAssignmentKind, UntypedClause, UntypedRecordUpdateArg, }, builtins::void, parser::token::Base, @@ -106,7 +106,7 @@ pub enum TypedExpr { tipo: Rc, value: Box, pattern: Pattern>, - kind: AssignmentKind, + kind: TypedAssignmentKind, }, Trace { @@ -519,7 +519,7 @@ pub enum UntypedExpr { location: Span, value: Box, pattern: Pattern<(), ()>, - kind: AssignmentKind, + kind: UntypedAssignmentKind, annotation: Option, }, @@ -1299,4 +1299,29 @@ impl UntypedExpr { Self::String { .. } | Self::UInt { .. } | Self::ByteArray { .. } ) } + + pub fn lambda(name: String, expressions: Vec, location: Span) -> Self { + Self::Fn { + location, + fn_style: FnStyle::Plain, + arguments: vec![Arg { + location, + doc: None, + annotation: None, + tipo: (), + arg_name: ArgName::Named { + label: name.clone(), + name, + location, + is_validator_param: false, + }, + }], + body: Self::Sequence { + location, + expressions, + } + .into(), + return_annotation: None, + } + } } diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index ee06815c..7217b138 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -4,9 +4,9 @@ use crate::{ CallArg, ClauseGuard, Constant, CurveType, DataType, Definition, Function, IfBranch, LogicalOpChainKind, ModuleConstant, Pattern, RecordConstructor, RecordConstructorArg, RecordUpdateSpread, Span, TraceKind, TypeAlias, TypedArg, UnOp, UnqualifiedImport, - UntypedArg, UntypedArgVia, UntypedClause, UntypedClauseGuard, UntypedDefinition, - UntypedFunction, UntypedModule, UntypedPattern, UntypedRecordUpdateArg, Use, Validator, - CAPTURE_VARIABLE, + UntypedArg, UntypedArgVia, UntypedAssignmentKind, UntypedClause, UntypedClauseGuard, + UntypedDefinition, UntypedFunction, UntypedModule, UntypedPattern, UntypedRecordUpdateArg, + Use, Validator, CAPTURE_VARIABLE, }, docvec, expr::{FnStyle, UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR}, @@ -681,14 +681,16 @@ impl<'comments> Formatter<'comments> { &mut self, pattern: &'a UntypedPattern, value: &'a UntypedExpr, - kind: AssignmentKind, + kind: UntypedAssignmentKind, annotation: &'a Option, ) -> Document<'a> { let keyword = match kind { - AssignmentKind::Let => "let", - AssignmentKind::Expect => "expect", + AssignmentKind::Let { .. } => "let", + AssignmentKind::Expect { .. } => "expect", }; + let symbol = if kind.is_backpassing() { "<-" } else { "=" }; + match pattern { UntypedPattern::Constructor { name, module: None, .. @@ -708,7 +710,8 @@ impl<'comments> Formatter<'comments> { .to_doc() .append(" ") .append(pattern.append(annotation).group()) - .append(" =") + .append(" ") + .append(symbol) .append(self.case_clause_value(value)) } } diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index 883bec27..cd13c5c0 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -559,7 +559,7 @@ impl<'a> CodeGenerator<'a> { &subject_type, AssignmentProperties { value_type: subject.tipo(), - kind: AssignmentKind::Let, + kind: AssignmentKind::let_(), remove_unused: false, full_check: false, msg_func: None, @@ -843,7 +843,7 @@ impl<'a> CodeGenerator<'a> { ) -> AirTree { assert!( match &value { - AirTree::Var { name, .. } if props.kind == AssignmentKind::Let => { + AirTree::Var { name, .. } if props.kind.is_let() => { name != "_" } _ => true, @@ -2812,7 +2812,7 @@ impl<'a> CodeGenerator<'a> { &actual_type, AssignmentProperties { value_type: data(), - kind: AssignmentKind::Expect, + kind: AssignmentKind::expect(), remove_unused: false, full_check: true, msg_func, diff --git a/crates/aiken-lang/src/gen_uplc/builder.rs b/crates/aiken-lang/src/gen_uplc/builder.rs index 8f365fd0..88eaf3d2 100644 --- a/crates/aiken-lang/src/gen_uplc/builder.rs +++ b/crates/aiken-lang/src/gen_uplc/builder.rs @@ -4,8 +4,8 @@ use super::{ }; use crate::{ ast::{ - AssignmentKind, BinOp, ClauseGuard, Constant, DataTypeKey, FunctionAccessKey, Pattern, - Span, TraceLevel, TypedArg, TypedClause, TypedClauseGuard, TypedDataType, TypedPattern, + BinOp, ClauseGuard, Constant, DataTypeKey, FunctionAccessKey, Pattern, Span, TraceLevel, + TypedArg, TypedAssignmentKind, TypedClause, TypedClauseGuard, TypedDataType, TypedPattern, UnOp, }, builtins::{bool, data, function, int, list, void}, @@ -68,7 +68,7 @@ pub enum HoistableFunction { #[derive(Clone, Debug)] pub struct AssignmentProperties { pub value_type: Rc, - pub kind: AssignmentKind, + pub kind: TypedAssignmentKind, pub remove_unused: bool, pub full_check: bool, pub msg_func: Option, diff --git a/crates/aiken-lang/src/parser/definition/snapshots/def_test_fail.snap b/crates/aiken-lang/src/parser/definition/snapshots/def_test_fail.snap index a2da8f18..ca43eda4 100644 --- a/crates/aiken-lang/src/parser/definition/snapshots/def_test_fail.snap +++ b/crates/aiken-lang/src/parser/definition/snapshots/def_test_fail.snap @@ -24,7 +24,9 @@ Test( with_spread: false, tipo: (), }, - kind: Expect, + kind: Expect { + backpassing: false, + }, annotation: None, }, Var { diff --git a/crates/aiken-lang/src/parser/definition/snapshots/function_assignment_only.snap b/crates/aiken-lang/src/parser/definition/snapshots/function_assignment_only.snap index 23f7613e..f01c224c 100644 --- a/crates/aiken-lang/src/parser/definition/snapshots/function_assignment_only.snap +++ b/crates/aiken-lang/src/parser/definition/snapshots/function_assignment_only.snap @@ -29,7 +29,9 @@ Fn( location: 17..18, name: "x", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, doc: None, diff --git a/crates/aiken-lang/src/parser/expr/assignment.rs b/crates/aiken-lang/src/parser/expr/assignment.rs index 30f8fbf5..8435e38f 100644 --- a/crates/aiken-lang/src/parser/expr/assignment.rs +++ b/crates/aiken-lang/src/parser/expr/assignment.rs @@ -1,10 +1,9 @@ -use chumsky::prelude::*; - use crate::{ ast, expr::UntypedExpr, parser::{annotation, error::ParseError, pattern, token::Token}, }; +use chumsky::prelude::*; pub fn let_( r: Recursive<'_, Token, UntypedExpr, ParseError>, @@ -12,9 +11,9 @@ pub fn let_( just(Token::Let) .ignore_then(pattern()) .then(just(Token::Colon).ignore_then(annotation()).or_not()) - .then_ignore(just(Token::Equal)) + .then(choice((just(Token::Equal), just(Token::LArrow)))) .then(r.clone()) - .validate(move |((pattern, annotation), value), span, emit| { + .validate(move |(((pattern, annotation), kind), value), span, emit| { if matches!(value, UntypedExpr::Assignment { .. }) { emit(ParseError::invalid_assignment_right_hand_side(span)) } @@ -23,7 +22,9 @@ pub fn let_( location: span, value: Box::new(value), pattern, - kind: ast::AssignmentKind::Let, + kind: ast::AssignmentKind::Let { + backpassing: kind == Token::LArrow, + }, annotation, } }) @@ -36,24 +37,27 @@ pub fn expect( .ignore_then( pattern() .then(just(Token::Colon).ignore_then(annotation()).or_not()) - .then_ignore(just(Token::Equal)) + .then(choice((just(Token::Equal), just(Token::LArrow)))) .or_not(), ) .then(r.clone()) .validate(move |(opt_pattern, value), span, emit| { - let (pattern, annotation) = opt_pattern.unwrap_or_else(|| { + let ((pattern, annotation), kind) = opt_pattern.unwrap_or_else(|| { ( - ast::UntypedPattern::Constructor { - is_record: false, - location: span, - name: "True".to_string(), - arguments: vec![], - module: None, - constructor: (), - with_spread: false, - tipo: (), - }, - None, + ( + ast::UntypedPattern::Constructor { + is_record: false, + location: span, + name: "True".to_string(), + arguments: vec![], + module: None, + constructor: (), + with_spread: false, + tipo: (), + }, + None, + ), + Token::Equal, ) }); @@ -65,7 +69,9 @@ pub fn expect( location: span, value: Box::new(value), pattern, - kind: ast::AssignmentKind::Expect, + kind: ast::AssignmentKind::Expect { + backpassing: kind == Token::LArrow, + }, annotation, } }) diff --git a/crates/aiken-lang/src/parser/expr/snapshots/block_let.snap b/crates/aiken-lang/src/parser/expr/snapshots/block_let.snap index cea26d50..4d930184 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/block_let.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/block_let.snap @@ -20,7 +20,9 @@ Assignment { location: 16..17, name: "x", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, BinOp { @@ -44,6 +46,8 @@ Assignment { location: 4..5, name: "b", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/expect.snap b/crates/aiken-lang/src/parser/expr/snapshots/expect.snap index 440e0855..db440339 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/expect.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/expect.snap @@ -31,6 +31,8 @@ Assignment { with_spread: false, tipo: (), }, - kind: Expect, + kind: Expect { + backpassing: false, + }, annotation: None, } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/expect_bool_sugar.snap b/crates/aiken-lang/src/parser/expr/snapshots/expect_bool_sugar.snap index 3f50b4f9..17011ba0 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/expect_bool_sugar.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/expect_bool_sugar.snap @@ -30,6 +30,8 @@ Assignment { with_spread: false, tipo: (), }, - kind: Expect, + kind: Expect { + backpassing: false, + }, annotation: None, } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/expect_expect_let.snap b/crates/aiken-lang/src/parser/expr/snapshots/expect_expect_let.snap index 2f2b1431..b5bba606 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/expect_expect_let.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/expect_expect_let.snap @@ -20,7 +20,9 @@ Assignment { location: 13..14, name: "a", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, ], @@ -35,6 +37,8 @@ Assignment { with_spread: false, tipo: (), }, - kind: Expect, + kind: Expect { + backpassing: false, + }, annotation: None, } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let.snap b/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let.snap index feaa148b..a4556c2c 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let.snap @@ -20,7 +20,9 @@ Assignment { location: 14..15, name: "b", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, ], @@ -29,6 +31,8 @@ Assignment { location: 4..5, name: "a", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let_parens.snap b/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let_parens.snap index 7ca8ace8..62f0b244 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let_parens.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let_parens.snap @@ -20,7 +20,9 @@ Assignment { location: 14..15, name: "b", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, ], @@ -29,6 +31,8 @@ Assignment { location: 4..5, name: "a", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let_return.snap b/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let_return.snap index 0d11cf5f..5127c302 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let_return.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/expect_let_in_let_return.snap @@ -20,7 +20,9 @@ Assignment { location: 16..17, name: "b", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, Var { @@ -33,6 +35,8 @@ Assignment { location: 4..5, name: "a", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/expect_trace_if_false.snap b/crates/aiken-lang/src/parser/expr/snapshots/expect_trace_if_false.snap index 7509df27..9417ee98 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/expect_trace_if_false.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/expect_trace_if_false.snap @@ -21,6 +21,8 @@ Assignment { with_spread: false, tipo: (), }, - kind: Expect, + kind: Expect { + backpassing: false, + }, annotation: None, } 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 a0af6bf1..d968e56b 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/function_invoke.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/function_invoke.snap @@ -31,7 +31,9 @@ Sequence { location: 4..5, name: "x", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, Assignment { @@ -115,7 +117,9 @@ Sequence { location: 24..33, name: "map_add_x", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, Call { diff --git a/crates/aiken-lang/src/parser/expr/snapshots/int_numeric_underscore.snap b/crates/aiken-lang/src/parser/expr/snapshots/int_numeric_underscore.snap index 5f1db19c..ed6577f4 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/int_numeric_underscore.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/int_numeric_underscore.snap @@ -18,7 +18,9 @@ Sequence { location: 8..9, name: "i", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, Assignment { @@ -34,7 +36,9 @@ Sequence { location: 28..29, name: "j", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, Assignment { @@ -54,7 +58,9 @@ Sequence { location: 48..49, name: "k", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, ], diff --git a/crates/aiken-lang/src/parser/expr/snapshots/let_bindings.snap b/crates/aiken-lang/src/parser/expr/snapshots/let_bindings.snap index ef8eef02..57c91fa6 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/let_bindings.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/let_bindings.snap @@ -32,6 +32,8 @@ Assignment { location: 4..9, name: "thing", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, } diff --git a/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple.snap b/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple.snap index 4fa94b9e..426f9ae4 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple.snap @@ -44,7 +44,9 @@ Sequence { location: 4..9, name: "tuple", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, BinOp { diff --git a/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple2.snap b/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple2.snap index ede9ec5a..f020b9d1 100644 --- a/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple2.snap +++ b/crates/aiken-lang/src/parser/expr/snapshots/parse_tuple2.snap @@ -31,7 +31,9 @@ Sequence { location: 4..5, name: "a", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, Tuple { diff --git a/crates/aiken-lang/src/parser/expr/when/snapshots/when_basic.snap b/crates/aiken-lang/src/parser/expr/when/snapshots/when_basic.snap index 53a3665a..57450895 100644 --- a/crates/aiken-lang/src/parser/expr/when/snapshots/when_basic.snap +++ b/crates/aiken-lang/src/parser/expr/when/snapshots/when_basic.snap @@ -89,7 +89,9 @@ When { location: 55..62, name: "amazing", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, Var { diff --git a/crates/aiken-lang/src/parser/lexer.rs b/crates/aiken-lang/src/parser/lexer.rs index 77fb21f2..c1fc1282 100644 --- a/crates/aiken-lang/src/parser/lexer.rs +++ b/crates/aiken-lang/src/parser/lexer.rs @@ -163,6 +163,8 @@ pub fn lexer() -> impl Parser, Error = ParseError> { just("!=").to(Token::NotEqual), just('!').to(Token::Bang), just('?').to(Token::Question), + just("<-").to(Token::LArrow), + just("->").to(Token::RArrow), choice(( just("<=").to(Token::LessEqual), just('<').to(Token::Less), @@ -170,7 +172,6 @@ pub fn lexer() -> impl Parser, Error = ParseError> { just('>').to(Token::Greater), )), just('+').to(Token::Plus), - just("->").to(Token::RArrow), just('-').to(Token::Minus), just('*').to(Token::Star), just('/').to(Token::Slash), diff --git a/crates/aiken-lang/src/parser/token.rs b/crates/aiken-lang/src/parser/token.rs index 607eeaf6..6169749f 100644 --- a/crates/aiken-lang/src/parser/token.rs +++ b/crates/aiken-lang/src/parser/token.rs @@ -62,6 +62,7 @@ pub enum Token { Pipe, // '|>' Dot, // '.' RArrow, // '->' + LArrow, // '<-' DotDot, // '..' EndOfFile, // Docs/Extra @@ -152,6 +153,7 @@ impl fmt::Display for Token { Token::Pipe => "|>", Token::Dot => ".", Token::RArrow => "->", + Token::LArrow => "<-", Token::DotDot => "..", Token::EndOfFile => "EOF", Token::Comment => "//", diff --git a/crates/aiken-lang/src/snapshots/function_ambiguous_sequence.snap b/crates/aiken-lang/src/snapshots/function_ambiguous_sequence.snap index 85167624..0c317312 100644 --- a/crates/aiken-lang/src/snapshots/function_ambiguous_sequence.snap +++ b/crates/aiken-lang/src/snapshots/function_ambiguous_sequence.snap @@ -23,7 +23,9 @@ Module { location: 19..20, name: "a", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, UInt { @@ -61,7 +63,9 @@ Module { location: 56..57, name: "a", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, UInt { @@ -110,7 +114,9 @@ Module { location: 93..94, name: "a", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, doc: None, @@ -155,7 +161,9 @@ Module { location: 126..127, name: "a", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, BinOp { diff --git a/crates/aiken-lang/src/snapshots/parse_unicode_offset_1.snap b/crates/aiken-lang/src/snapshots/parse_unicode_offset_1.snap index a0708406..9d78faff 100644 --- a/crates/aiken-lang/src/snapshots/parse_unicode_offset_1.snap +++ b/crates/aiken-lang/src/snapshots/parse_unicode_offset_1.snap @@ -28,7 +28,9 @@ Module { location: 17..18, name: "x", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, Var { diff --git a/crates/aiken-lang/src/snapshots/parse_unicode_offset_2.snap b/crates/aiken-lang/src/snapshots/parse_unicode_offset_2.snap index 699df711..a4dd2337 100644 --- a/crates/aiken-lang/src/snapshots/parse_unicode_offset_2.snap +++ b/crates/aiken-lang/src/snapshots/parse_unicode_offset_2.snap @@ -26,7 +26,9 @@ Module { location: 17..18, name: "x", }, - kind: Let, + kind: Let { + backpassing: false, + }, annotation: None, }, Var { diff --git a/crates/aiken-lang/src/tests/check.rs b/crates/aiken-lang/src/tests/check.rs index 33afd01a..11a5c861 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -1185,6 +1185,204 @@ fn trace_if_false_ok() { assert!(check(parse(source_code)).is_ok()) } +#[test] +fn backpassing_basic() { + let source_code = r#" + fn and_then(opt: Option, then: fn(a) -> Option) -> Option { + when opt is { + None -> None + Some(a) -> then(a) + } + } + + fn backpassing(opt_i: Option, opt_j: Option) -> Option { + let i <- and_then(opt_i) + let j <- and_then(opt_j) + Some(i + j) + } + "#; + + assert!(check(parse(source_code)).is_ok()) +} + +#[test] +fn backpassing_expect_simple() { + let source_code = r#" + fn and_then(opt: Option, then: fn(a) -> Option) -> Option { + when opt is { + None -> None + Some(a) -> then(a) + } + } + + fn backpassing(opt_i: Option, opt_j: Option) -> Option { + expect 42 <- and_then(opt_i) + let j <- and_then(opt_j) + Some(j + 42) + } + "#; + + assert!(check(parse(source_code)).is_ok()) +} + +#[test] +fn backpassing_expect_nested() { + let source_code = r#" + fn and_then(opt: Option, then: fn(Option) -> Option) -> Option { + when opt is { + None -> None + Some(a) -> then(Some(a)) + } + } + + fn backpassing(opt_i: Option, opt_j: Option) -> Option { + expect Some(i) <- and_then(opt_i) + expect Some(j) <- and_then(opt_j) + Some(i + j) + } + "#; + + assert!(check(parse(source_code)).is_ok()) +} + +#[test] +fn backpassing_interleaved_capture() { + let source_code = r#" + fn and_then(opt: Option, then: fn(a) -> Option) -> Option { + when opt is { + None -> None + Some(a) -> then(a) + } + } + + fn backpassing(opt_i: Option, opt_j: Option) -> Option { + let f = and_then(opt_i, _) + let i <- f + let g = and_then(opt_j, _) + let j <- g + Some(i + j) + } + "#; + + assert!(check(parse(source_code)).is_ok()) +} + +#[test] +fn backpassing_patterns() { + let source_code = r#" + fn and_then(opt: Option, then: fn(a) -> Option) -> Option { + when opt is { + None -> None + Some(a) -> then(a) + } + } + + type Foo { + foo: Int, + } + + fn backpassing(opt_i: Option, opt_j: Option) -> Option { + let Foo { foo: i } <- and_then(opt_i) + let Foo { foo: j } <- and_then(opt_j) + Some(i + j) + } + "#; + + assert!(check(parse(source_code)).is_ok()) +} + +#[test] +fn backpassing_not_a_function() { + let source_code = r#" + fn and_then(opt: Option, then: fn(a) -> Option) -> Option { + when opt is { + None -> None + Some(a) -> then(a) + } + } + + fn backpassing(opt_i: Option, opt_j: Option) -> Option { + let i <- opt_i + let j <- and_then(opt_j) + Some(i + j) + } + "#; + + assert!(matches!( + check(parse(source_code)), + Err((_, Error::NotFn { .. })) + )) +} + +#[test] +fn backpassing_non_exhaustive_pattern() { + let source_code = r#" + fn and_then(opt: Option, then: fn(a) -> Option) -> Option { + when opt is { + None -> None + Some(a) -> then(a) + } + } + + fn backpassing(opt_i: Option, opt_j: Option) -> Option { + let 42 <- and_then(opt_i) + let j <- and_then(opt_j) + Some(i + j) + } + "#; + + assert!(matches!( + check(parse(source_code)), + Err((_, Error::NotExhaustivePatternMatch { .. })) + )) +} + +#[test] +fn backpassing_unsaturated_fn() { + let source_code = r#" + fn and_then(opt: Option, then: fn(a) -> Option) -> Option { + when opt is { + None -> None + Some(a) -> then(a) + } + } + + fn backpassing(opt_i: Option, opt_j: Option) -> Option { + let i <- and_then + let j <- and_then(opt_j) + Some(i + j) + } + "#; + + assert!(matches!( + check(parse(source_code)), + Err((_, Error::IncorrectFieldsArity { .. })) + )) +} + +#[test] +fn backpassing_expect_type_mismatch() { + let source_code = r#" + fn and_then(opt: Option, then: fn(a) -> Option) -> Option { + when opt is { + None -> None + Some(a) -> then(a) + } + } + + fn backpassing(opt_i: Option, opt_j: Option) -> Option { + expect Some(i) <- and_then(opt_i) + let j <- and_then(opt_j) + Some(i + j) + } + "#; + + assert!(matches!( + check(parse(source_code)), + Err((_, Error::CouldNotUnify { .. })) + )) +} + #[test] fn trace_if_false_ko() { let source_code = r#" diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index d78a296d..b9a298e4 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -8,12 +8,12 @@ use super::{ }; use crate::{ ast::{ - Annotation, Arg, ArgName, AssignmentKind, BinOp, Bls12_381Point, ByteArrayFormatPreference, - CallArg, ClauseGuard, Constant, Curve, IfBranch, LogicalOpChainKind, Pattern, - RecordUpdateSpread, Span, TraceKind, TraceLevel, Tracing, TypedArg, TypedCallArg, - TypedClause, TypedClauseGuard, TypedIfBranch, TypedPattern, TypedRecordUpdateArg, UnOp, - UntypedArg, UntypedClause, UntypedClauseGuard, UntypedIfBranch, UntypedPattern, - UntypedRecordUpdateArg, + self, Annotation, Arg, ArgName, AssignmentKind, BinOp, Bls12_381Point, + ByteArrayFormatPreference, CallArg, ClauseGuard, Constant, Curve, IfBranch, + LogicalOpChainKind, Pattern, RecordUpdateSpread, Span, TraceKind, TraceLevel, Tracing, + TypedArg, TypedCallArg, TypedClause, TypedClauseGuard, TypedIfBranch, TypedPattern, + TypedRecordUpdateArg, UnOp, UntypedArg, UntypedAssignmentKind, UntypedClause, + UntypedClauseGuard, UntypedIfBranch, UntypedPattern, UntypedRecordUpdateArg, }, builtins::{ bool, byte_array, function, g1_element, g2_element, int, list, string, tuple, void, @@ -916,7 +916,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { &mut self, untyped_pattern: UntypedPattern, untyped_value: UntypedExpr, - kind: AssignmentKind, + kind: UntypedAssignmentKind, annotation: &Option, location: Span, ) -> Result { @@ -978,12 +978,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> { // If `expect` is explicitly used, we still check exhaustiveness but instead of returning an // error we emit a warning which explains that using `expect` is unnecessary. match kind { - AssignmentKind::Let => { + AssignmentKind::Let { .. } => { self.environment .check_exhaustiveness(&[&pattern], location, true)? } - AssignmentKind::Expect => { + AssignmentKind::Expect { .. } => { let is_exaustive_pattern = self .environment .check_exhaustiveness(&[&pattern], location, false) @@ -999,12 +999,26 @@ impl<'a, 'b> ExprTyper<'a, 'b> { }, pattern_location: untyped_pattern.location(), value_location: untyped_value.location(), - sample: UntypedExpr::Assignment { - location: Span::empty(), - value: Box::new(untyped_value), - pattern: untyped_pattern, - kind: AssignmentKind::Let, - annotation: None, + sample: match untyped_value { + UntypedExpr::Var { name, .. } if name == ast::BACKPASS_VARIABLE => { + UntypedExpr::Assignment { + location: Span::empty(), + value: Box::new(UntypedExpr::Var { + name: "...".to_string(), + location: Span::empty(), + }), + pattern: untyped_pattern, + kind: AssignmentKind::Let { backpassing: true }, + annotation: None, + } + } + _ => UntypedExpr::Assignment { + location: Span::empty(), + value: Box::new(untyped_value), + pattern: untyped_pattern, + kind: AssignmentKind::let_(), + annotation: None, + }, }, }); } @@ -1014,7 +1028,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { Ok(TypedExpr::Assignment { location, tipo: value_typ, - kind, + kind: kind.into(), pattern, value: Box::new(typed_value), }) @@ -1707,13 +1721,157 @@ impl<'a, 'b> ExprTyper<'a, 'b> { PipeTyper::infer(self, expressions) } + fn backpass(&mut self, breakpoint: UntypedExpr, continuation: Vec) -> UntypedExpr { + let (value, pattern, annotation, kind, assign_location) = match breakpoint { + UntypedExpr::Assignment { + location, + value, + pattern, + annotation, + kind, + .. + } => (value, pattern, annotation, kind, location), + _ => unreachable!("backpass misuse: breakpoint isn't an Assignment ?!"), + }; + + let value_location = value.location(); + + let call_location = Span { + start: value_location.end, + end: continuation + .last() + .map(|expr| expr.location().end) + .unwrap_or_else(|| value_location.end), + }; + + // In case where we have a Pattern that isn't simply a let-binding to a name, we do insert an extra let-binding + // in front of the continuation sequence. This is because we do not support patterns in function argument + // (which is perhaps something we should support?). + let (name, continuation) = match pattern { + Pattern::Var { name, .. } | Pattern::Discard { name, .. } if kind.is_let() => { + (name.clone(), continuation) + } + _ => { + let mut with_assignment = vec![UntypedExpr::Assignment { + location: assign_location, + value: UntypedExpr::Var { + location: value_location, + name: ast::BACKPASS_VARIABLE.to_string(), + } + .into(), + pattern, + // Erase backpassing while preserving assignment kind. + kind: match kind { + AssignmentKind::Let { .. } => AssignmentKind::let_(), + AssignmentKind::Expect { .. } => AssignmentKind::expect(), + }, + annotation, + }]; + with_assignment.extend(continuation); + (ast::BACKPASS_VARIABLE.to_string(), with_assignment) + } + }; + + match *value { + UntypedExpr::Call { fun, arguments, .. } => { + let mut new_arguments = Vec::new(); + new_arguments.extend(arguments); + new_arguments.push(CallArg { + location: call_location, + label: None, + value: UntypedExpr::lambda(name, continuation, call_location), + }); + + UntypedExpr::Call { + location: call_location, + fun, + arguments: new_arguments, + } + } + + // This typically occurs on function captures. We do not try to assert anything on the + // length of the arguments here. We defer that to the rest of the type-checker. The + // only thing we have to do is rewrite the AST as-if someone had passed a callback. + // + // Now, whether this leads to an invalid call usage, that's not *our* immediate + // problem. + UntypedExpr::Fn { + fn_style, + ref arguments, + ref return_annotation, + .. + } => { + let return_annotation = return_annotation.clone(); + + let arguments = arguments.iter().skip(1).cloned().collect::>(); + + let call = UntypedExpr::Call { + location: call_location, + fun: value, + arguments: vec![CallArg { + location: call_location, + label: None, + value: UntypedExpr::lambda(name, continuation, call_location), + }], + }; + + if arguments.is_empty() { + call + } else { + UntypedExpr::Fn { + location: call_location, + fn_style, + arguments, + body: call.into(), + return_annotation, + } + } + } + + // Similarly to function captures, if we have any other expression we simply call it + // with our continuation. If the expression isn't callable? No problem, the + // type-checker will catch that eventually in exactly the same way as if the code was + // written like that to begin with. + _ => UntypedExpr::Call { + location: call_location, + fun: value, + arguments: vec![CallArg { + location: call_location, + label: None, + value: UntypedExpr::lambda(name, continuation, call_location), + }], + }, + } + } + fn infer_seq(&mut self, location: Span, untyped: Vec) -> Result { + // Search for backpassing. + let mut breakpoint = None; + let mut prefix = Vec::with_capacity(untyped.len()); + let mut suffix = Vec::with_capacity(untyped.len()); + for expression in untyped.into_iter() { + if breakpoint.is_some() { + suffix.push(expression); + } else { + match expression { + UntypedExpr::Assignment { kind, .. } if kind.is_backpassing() => { + breakpoint = Some(expression); + } + _ => prefix.push(expression), + } + } + } + if let Some(breakpoint) = breakpoint { + prefix.push(self.backpass(breakpoint, suffix)); + return self.infer_seq(location, prefix); + } + let sequence = self.in_new_scope(|scope| { - let count = untyped.len(); + let count = prefix.len(); let mut expressions = Vec::with_capacity(count); - for (i, expression) in untyped.into_iter().enumerate() { + for (i, expression) in prefix.into_iter().enumerate() { let no_assignment = assert_no_assignment(&expression); let typed_expression = scope.infer(expression)?; @@ -1992,7 +2150,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { location: Span::empty(), value: Box::new(subject.clone()), pattern: clauses[0].patterns[0].clone(), - kind: AssignmentKind::Let, + kind: AssignmentKind::let_(), annotation: None, }, }); @@ -2120,7 +2278,7 @@ fn assert_assignment(expr: TypedExpr) -> Result { with_spread: false, tipo: void(), }, - kind: AssignmentKind::Let, + kind: AssignmentKind::let_(), }); } diff --git a/crates/aiken-lang/src/tipo/pipe.rs b/crates/aiken-lang/src/tipo/pipe.rs index d615cc85..613ce5ab 100644 --- a/crates/aiken-lang/src/tipo/pipe.rs +++ b/crates/aiken-lang/src/tipo/pipe.rs @@ -1,18 +1,15 @@ -use std::{ops::Deref, rc::Rc}; - -use vec1::Vec1; - -use crate::{ - ast::{AssignmentKind, CallArg, Pattern, Span, PIPE_VARIABLE}, - builtins::function, - expr::{TypedExpr, UntypedExpr}, -}; - use super::{ error::{Error, UnifyErrorSituation}, expr::ExprTyper, Type, ValueConstructor, ValueConstructorVariant, }; +use crate::{ + ast::{AssignmentKind, CallArg, Pattern, Span, PIPE_VARIABLE}, + builtins::function, + expr::{TypedExpr, UntypedExpr}, +}; +use std::{ops::Deref, rc::Rc}; +use vec1::Vec1; #[derive(Debug)] pub(crate) struct PipeTyper<'a, 'b, 'c> { @@ -184,7 +181,7 @@ impl<'a, 'b, 'c> PipeTyper<'a, 'b, 'c> { let assignment = TypedExpr::Assignment { location, tipo: expression.tipo(), - kind: AssignmentKind::Let, + kind: AssignmentKind::let_(), value: Box::new(expression), pattern: Pattern::Var { location,