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,