From 1f530f3b2466edb77295246630c064395039a62e Mon Sep 17 00:00:00 2001 From: KtorZ Date: Sun, 10 Mar 2024 11:26:41 +0100 Subject: [PATCH 1/5] Experiment with monadic bind. --- crates/aiken-lang/src/ast.rs | 2 + crates/aiken-lang/src/format.rs | 16 ++-- .../aiken-lang/src/parser/expr/assignment.rs | 13 ++- crates/aiken-lang/src/parser/lexer.rs | 3 +- crates/aiken-lang/src/parser/token.rs | 2 + crates/aiken-lang/src/tipo/expr.rs | 93 ++++++++++++++++++- 6 files changed, 109 insertions(+), 20 deletions(-) diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 203a242d..409b7304 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -1425,6 +1425,7 @@ impl Default for Bls12_381Point { #[derive(Debug, Clone, PartialEq, Eq, Copy, serde::Serialize, serde::Deserialize)] pub enum AssignmentKind { Let, + Bind, Expect, } @@ -1440,6 +1441,7 @@ impl AssignmentKind { pub fn location_offset(&self) -> usize { match self { AssignmentKind::Let => 3, + AssignmentKind::Bind => 3, AssignmentKind::Expect => 6, } } diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index ee06815c..33861153 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -684,9 +684,10 @@ impl<'comments> Formatter<'comments> { kind: AssignmentKind, annotation: &'a Option, ) -> Document<'a> { - let keyword = match kind { - AssignmentKind::Let => "let", - AssignmentKind::Expect => "expect", + let (keyword, equal) = match kind { + AssignmentKind::Let => ("let", "="), + AssignmentKind::Bind => ("let", "<-"), + AssignmentKind::Expect => ("expect", "="), }; match pattern { @@ -708,7 +709,8 @@ impl<'comments> Formatter<'comments> { .to_doc() .append(" ") .append(pattern.append(annotation).group()) - .append(" =") + .append(" ") + .append(equal) .append(self.case_clause_value(value)) } } @@ -1842,11 +1844,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/expr/assignment.rs b/crates/aiken-lang/src/parser/expr/assignment.rs index 30f8fbf5..74d77e77 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,11 @@ pub fn let_( location: span, value: Box::new(value), pattern, - kind: ast::AssignmentKind::Let, + kind: if kind == Token::LArrow { + ast::AssignmentKind::Bind + } else { + ast::AssignmentKind::Let + }, annotation, } }) 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/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index d78a296d..fe706e08 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -24,7 +24,7 @@ use crate::{ tipo::{fields::FieldMap, PatternConstructor, TypeVar}, }; use std::{cmp::Ordering, collections::HashMap, ops::Deref, rc::Rc}; -use vec1::Vec1; +use vec1::{vec1, Vec1}; #[derive(Debug)] pub(crate) struct ExprTyper<'a, 'b> { @@ -978,6 +978,10 @@ 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::Bind => { + unreachable!("monadic-bind should have been desugared earlier.") + } + AssignmentKind::Let => { self.environment .check_exhaustiveness(&[&pattern], location, true)? @@ -1708,15 +1712,26 @@ impl<'a, 'b> ExprTyper<'a, 'b> { } fn infer_seq(&mut self, location: Span, untyped: Vec) -> Result { - let sequence = self.in_new_scope(|scope| { + let mut breakpoint = None; + + let mut sequence = self.in_new_scope(|scope| { let count = untyped.len(); let mut expressions = Vec::with_capacity(count); - for (i, expression) in untyped.into_iter().enumerate() { - let no_assignment = assert_no_assignment(&expression); + for (i, expression) in untyped.iter().enumerate() { + let no_assignment = assert_no_assignment(expression); - let typed_expression = scope.infer(expression)?; + let typed_expression = match expression { + UntypedExpr::Assignment { + kind: AssignmentKind::Bind, + .. + } => { + breakpoint = Some((i, expression.clone())); + return Ok(expressions); + } + _ => scope.infer(expression.to_owned())?, + }; expressions.push(match i.cmp(&(count - 1)) { // When the expression is the last in a sequence, we enforce it is NOT @@ -1738,6 +1753,74 @@ impl<'a, 'b> ExprTyper<'a, 'b> { Ok(expressions) })?; + if let Some(( + i, + UntypedExpr::Assignment { + location, + value, + pattern, + .. + }, + )) = breakpoint + { + let then = UntypedExpr::Sequence { + location, + expressions: untyped.into_iter().skip(i + 1).collect::>(), + }; + + // TODO: This must be constructed based on the inferred type of *value*. + // + // let tipo = self.infer(untyped_value.clone())?.tipo(); + // + // The following is the `and_then` for Option. The one for Fuzzer is a bit + // different. + let desugar = UntypedExpr::When { + location, + subject: value.clone(), + clauses: vec![ + UntypedClause { + location, + guard: None, + patterns: vec1![Pattern::Constructor { + location, + is_record: false, + with_spread: false, + name: "None".to_string(), + module: None, + constructor: (), + tipo: (), + arguments: vec![], + }], + then: UntypedExpr::Var { + location, + name: "None".to_string(), + }, + }, + UntypedClause { + location, + guard: None, + patterns: vec1![Pattern::Constructor { + location, + is_record: false, + with_spread: false, + name: "Some".to_string(), + module: None, + constructor: (), + tipo: (), + arguments: vec![CallArg { + location, + label: None, + value: pattern.clone(), + }], + }], + then, + }, + ], + }; + + sequence.push(self.infer(desugar)?); + }; + let unused = self .environment .warnings From df898bf239be4c3d8cf1f95bcdde6d31811ffe3c Mon Sep 17 00:00:00 2001 From: KtorZ Date: Sun, 10 Mar 2024 14:05:56 +0100 Subject: [PATCH 2/5] Rework monadic-bind into function backpassing. This is more holistic and less awkward than having monadic bind working only with some pre-defined type. Backpassing work with _any_ function, and can be implemented relatively easily by rewriting the AST on-the-fly. Also, it is far easier to explain than trying to explain what a monadic bind is, how its behavior differs from type to type and why it isn't generally available for any monadic type. --- CHANGELOG.md | 1 + crates/aiken-lang/src/ast.rs | 14 ++ crates/aiken-lang/src/expr.rs | 35 +++- crates/aiken-lang/src/format.rs | 6 +- crates/aiken-lang/src/tests/check.rs | 135 +++++++++++++++ crates/aiken-lang/src/tipo/expr.rs | 237 +++++++++++++++++---------- 6 files changed, 331 insertions(+), 97 deletions(-) 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 409b7304..c603b685 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); } diff --git a/crates/aiken-lang/src/expr.rs b/crates/aiken-lang/src/expr.rs index 28db5a8a..7c310fe4 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, 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, }, builtins::void, parser::token::Base, @@ -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 33861153..e5c67046 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -1844,7 +1844,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/tests/check.rs b/crates/aiken-lang/src/tests/check.rs index 33afd01a..89184c28 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -1185,6 +1185,141 @@ 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_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 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 fe706e08..8608b2f6 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, UntypedClause, UntypedClauseGuard, UntypedIfBranch, + UntypedPattern, UntypedRecordUpdateArg, }, builtins::{ bool, byte_array, function, g1_element, g2_element, int, list, string, tuple, void, @@ -24,7 +24,7 @@ use crate::{ tipo::{fields::FieldMap, PatternConstructor, TypeVar}, }; use std::{cmp::Ordering, collections::HashMap, ops::Deref, rc::Rc}; -use vec1::{vec1, Vec1}; +use vec1::Vec1; #[derive(Debug)] pub(crate) struct ExprTyper<'a, 'b> { @@ -1711,27 +1711,150 @@ impl<'a, 'b> ExprTyper<'a, 'b> { PipeTyper::infer(self, expressions) } - fn infer_seq(&mut self, location: Span, untyped: Vec) -> Result { - let mut breakpoint = None; + fn backpass(&mut self, breakpoint: UntypedExpr, continuation: Vec) -> UntypedExpr { + let (assign_location, value, pattern, annotation) = match breakpoint { + UntypedExpr::Assignment { + location, + value, + pattern, + annotation, + .. + } => (location, value, pattern, annotation), + _ => unreachable!("backpass misuse: breakpoint isn't an Assignment ?!"), + }; - let mut sequence = self.in_new_scope(|scope| { - let count = untyped.len(); + // 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, .. } => { + (name.clone(), continuation) + } + _ => { + let mut with_assignment = vec![UntypedExpr::Assignment { + location: assign_location, + value: UntypedExpr::Var { + location: assign_location, + name: ast::BACKPASS_VARIABLE.to_string(), + } + .into(), + pattern, + kind: AssignmentKind::Let, + annotation, + }]; + with_assignment.extend(continuation); + (ast::BACKPASS_VARIABLE.to_string(), with_assignment) + } + }; + + match *value { + UntypedExpr::Call { + location: call_location, + fun, + arguments, + } => { + let mut new_arguments = Vec::new(); + new_arguments.extend(arguments); + new_arguments.push(CallArg { + location: assign_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 { + location: call_location, + 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: assign_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: assign_location, + fun: value, + arguments: vec![CallArg { + location: assign_location, + label: None, + value: UntypedExpr::lambda(name, continuation, assign_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() { + match expression { + _ if breakpoint.is_some() => suffix.push(expression), + UntypedExpr::Assignment { + kind: AssignmentKind::Bind, + .. + } => { + 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 = prefix.len(); let mut expressions = Vec::with_capacity(count); - for (i, expression) in untyped.iter().enumerate() { - let no_assignment = assert_no_assignment(expression); + for (i, expression) in prefix.into_iter().enumerate() { + let no_assignment = assert_no_assignment(&expression); - let typed_expression = match expression { - UntypedExpr::Assignment { - kind: AssignmentKind::Bind, - .. - } => { - breakpoint = Some((i, expression.clone())); - return Ok(expressions); - } - _ => scope.infer(expression.to_owned())?, - }; + let typed_expression = scope.infer(expression)?; expressions.push(match i.cmp(&(count - 1)) { // When the expression is the last in a sequence, we enforce it is NOT @@ -1753,74 +1876,6 @@ impl<'a, 'b> ExprTyper<'a, 'b> { Ok(expressions) })?; - if let Some(( - i, - UntypedExpr::Assignment { - location, - value, - pattern, - .. - }, - )) = breakpoint - { - let then = UntypedExpr::Sequence { - location, - expressions: untyped.into_iter().skip(i + 1).collect::>(), - }; - - // TODO: This must be constructed based on the inferred type of *value*. - // - // let tipo = self.infer(untyped_value.clone())?.tipo(); - // - // The following is the `and_then` for Option. The one for Fuzzer is a bit - // different. - let desugar = UntypedExpr::When { - location, - subject: value.clone(), - clauses: vec![ - UntypedClause { - location, - guard: None, - patterns: vec1![Pattern::Constructor { - location, - is_record: false, - with_spread: false, - name: "None".to_string(), - module: None, - constructor: (), - tipo: (), - arguments: vec![], - }], - then: UntypedExpr::Var { - location, - name: "None".to_string(), - }, - }, - UntypedClause { - location, - guard: None, - patterns: vec1![Pattern::Constructor { - location, - is_record: false, - with_spread: false, - name: "Some".to_string(), - module: None, - constructor: (), - tipo: (), - arguments: vec![CallArg { - location, - label: None, - value: pattern.clone(), - }], - }], - then, - }, - ], - }; - - sequence.push(self.infer(desugar)?); - }; - let unused = self .environment .warnings From 435dd0d213a7f89e33f4952877754de4af65641d Mon Sep 17 00:00:00 2001 From: KtorZ Date: Sun, 10 Mar 2024 23:04:07 +0100 Subject: [PATCH 3/5] Refactor AssignmentKind to allow backpassing on both let and expect. The 3rd kind of assignment kind (Bind) is gone and now reflected through a boolean parameter. Note that this parameter is completely erased by the type-checker so that the rest of the pipeline (i.e. code-generation) doesn't have to make any assumption. They simply can't see a backpassing let or expect. --- crates/aiken-lang/src/ast.rs | 48 +++++++++++++++---- crates/aiken-lang/src/expr.rs | 14 +++--- crates/aiken-lang/src/format.rs | 19 ++++---- crates/aiken-lang/src/gen_uplc.rs | 6 +-- crates/aiken-lang/src/gen_uplc/builder.rs | 6 +-- .../definition/snapshots/def_test_fail.snap | 4 +- .../snapshots/function_assignment_only.snap | 4 +- .../aiken-lang/src/parser/expr/assignment.rs | 39 ++++++++------- .../src/parser/expr/snapshots/block_let.snap | 8 +++- .../src/parser/expr/snapshots/expect.snap | 4 +- .../expr/snapshots/expect_bool_sugar.snap | 4 +- .../expr/snapshots/expect_expect_let.snap | 8 +++- .../expr/snapshots/expect_let_in_let.snap | 8 +++- .../snapshots/expect_let_in_let_parens.snap | 8 +++- .../snapshots/expect_let_in_let_return.snap | 8 +++- .../expr/snapshots/expect_trace_if_false.snap | 4 +- .../expr/snapshots/function_invoke.snap | 8 +++- .../snapshots/int_numeric_underscore.snap | 12 +++-- .../parser/expr/snapshots/let_bindings.snap | 4 +- .../parser/expr/snapshots/parse_tuple.snap | 4 +- .../parser/expr/snapshots/parse_tuple2.snap | 4 +- .../expr/when/snapshots/when_basic.snap | 4 +- .../function_ambiguous_sequence.snap | 16 +++++-- .../src/snapshots/parse_unicode_offset_1.snap | 4 +- .../src/snapshots/parse_unicode_offset_2.snap | 4 +- crates/aiken-lang/src/tipo/expr.rs | 40 +++++++--------- crates/aiken-lang/src/tipo/pipe.rs | 19 ++++---- 27 files changed, 198 insertions(+), 113 deletions(-) diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index c603b685..b326d123 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -1436,31 +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, - Bind, - 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::Bind => 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: () } + } + + pub fn expect() -> Self { + AssignmentKind::Expect { backpassing: () } + } +} + pub type MultiPattern = Vec>; pub type UntypedMultiPattern = MultiPattern<(), ()>; diff --git a/crates/aiken-lang/src/expr.rs b/crates/aiken-lang/src/expr.rs index 7c310fe4..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, ArgName, 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, }, diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index e5c67046..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,15 +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, equal) = match kind { - AssignmentKind::Let => ("let", "="), - AssignmentKind::Bind => ("let", "<-"), - AssignmentKind::Expect => ("expect", "="), + let keyword = match kind { + AssignmentKind::Let { .. } => "let", + AssignmentKind::Expect { .. } => "expect", }; + let symbol = if kind.is_backpassing() { "<-" } else { "=" }; + match pattern { UntypedPattern::Constructor { name, module: None, .. @@ -710,7 +711,7 @@ impl<'comments> Formatter<'comments> { .append(" ") .append(pattern.append(annotation).group()) .append(" ") - .append(equal) + .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 74d77e77..8435e38f 100644 --- a/crates/aiken-lang/src/parser/expr/assignment.rs +++ b/crates/aiken-lang/src/parser/expr/assignment.rs @@ -22,10 +22,8 @@ pub fn let_( location: span, value: Box::new(value), pattern, - kind: if kind == Token::LArrow { - ast::AssignmentKind::Bind - } else { - ast::AssignmentKind::Let + kind: ast::AssignmentKind::Let { + backpassing: kind == Token::LArrow, }, annotation, } @@ -39,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, ) }); @@ -68,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/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/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 8608b2f6..166e1b54 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -12,8 +12,8 @@ use crate::{ 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, + 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,16 +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::Bind => { - unreachable!("monadic-bind should have been desugared earlier.") - } - - 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) @@ -1007,7 +1003,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { location: Span::empty(), value: Box::new(untyped_value), pattern: untyped_pattern, - kind: AssignmentKind::Let, + kind: AssignmentKind::Let { backpassing: false }, annotation: None, }, }); @@ -1018,7 +1014,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { Ok(TypedExpr::Assignment { location, tipo: value_typ, - kind, + kind: kind.into(), pattern, value: Box::new(typed_value), }) @@ -1739,7 +1735,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { } .into(), pattern, - kind: AssignmentKind::Let, + kind: AssignmentKind::Let { backpassing: false }, annotation, }]; with_assignment.extend(continuation); @@ -1830,15 +1826,15 @@ impl<'a, 'b> ExprTyper<'a, 'b> { let mut prefix = Vec::with_capacity(untyped.len()); let mut suffix = Vec::with_capacity(untyped.len()); for expression in untyped.into_iter() { - match expression { - _ if breakpoint.is_some() => suffix.push(expression), - UntypedExpr::Assignment { - kind: AssignmentKind::Bind, - .. - } => { - breakpoint = Some(expression); + if breakpoint.is_some() { + suffix.push(expression); + } else { + match expression { + UntypedExpr::Assignment { kind, .. } if kind.is_backpassing() => { + breakpoint = Some(expression); + } + _ => prefix.push(expression), } - _ => prefix.push(expression), } } if let Some(breakpoint) = breakpoint { @@ -2130,7 +2126,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 { backpassing: false }, annotation: None, }, }); @@ -2258,7 +2254,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, From a57dcf3307b8987d921ba7b11bfee4c6ac415575 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Mon, 11 Mar 2024 00:15:53 +0100 Subject: [PATCH 4/5] Allow backpassing with expect. --- crates/aiken-lang/src/ast.rs | 10 +++-- crates/aiken-lang/src/tests/check.rs | 63 ++++++++++++++++++++++++++++ crates/aiken-lang/src/tipo/expr.rs | 17 +++++--- 3 files changed, 81 insertions(+), 9 deletions(-) diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index b326d123..1760ec53 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -1479,13 +1479,17 @@ impl AssignmentKind { } } -impl AssignmentKind<()> { +impl AssignmentKind { pub fn let_() -> Self { - AssignmentKind::Let { backpassing: () } + AssignmentKind::Let { + backpassing: Default::default(), + } } pub fn expect() -> Self { - AssignmentKind::Expect { backpassing: () } + AssignmentKind::Expect { + backpassing: Default::default(), + } } } diff --git a/crates/aiken-lang/src/tests/check.rs b/crates/aiken-lang/src/tests/check.rs index 89184c28..11a5c861 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -1205,6 +1205,46 @@ fn backpassing_basic() { 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#" @@ -1320,6 +1360,29 @@ fn backpassing_unsaturated_fn() { )) } +#[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 166e1b54..5fa3dcae 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -1708,14 +1708,15 @@ impl<'a, 'b> ExprTyper<'a, 'b> { } fn backpass(&mut self, breakpoint: UntypedExpr, continuation: Vec) -> UntypedExpr { - let (assign_location, value, pattern, annotation) = match breakpoint { + let (value, pattern, annotation, kind, assign_location) = match breakpoint { UntypedExpr::Assignment { location, value, pattern, annotation, + kind, .. - } => (location, value, pattern, annotation), + } => (value, pattern, annotation, kind, location), _ => unreachable!("backpass misuse: breakpoint isn't an Assignment ?!"), }; @@ -1723,19 +1724,23 @@ impl<'a, 'b> ExprTyper<'a, 'b> { // 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, .. } => { + 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: assign_location, + location: value_location, name: ast::BACKPASS_VARIABLE.to_string(), } .into(), pattern, - kind: AssignmentKind::Let { backpassing: false }, + // Erase backpassing while preserving assignment kind. + kind: match kind { + AssignmentKind::Let { .. } => AssignmentKind::let_(), + AssignmentKind::Expect { .. } => AssignmentKind::expect(), + }, annotation, }]; with_assignment.extend(continuation); @@ -2126,7 +2131,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { location: Span::empty(), value: Box::new(subject.clone()), pattern: clauses[0].patterns[0].clone(), - kind: AssignmentKind::Let { backpassing: false }, + kind: AssignmentKind::let_(), annotation: None, }, }); From 7e8e959251f7e85c28b4922fd8bcb8837bbb62bf Mon Sep 17 00:00:00 2001 From: KtorZ Date: Mon, 11 Mar 2024 00:16:08 +0100 Subject: [PATCH 5/5] Fix spans and error reporting for backpassing. --- crates/aiken-lang/src/tipo/expr.rs | 53 ++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 5fa3dcae..b9a298e4 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -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 { backpassing: false }, - 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, + }, }, }); } @@ -1720,6 +1734,16 @@ impl<'a, 'b> ExprTyper<'a, 'b> { _ => 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?). @@ -1749,15 +1773,11 @@ impl<'a, 'b> ExprTyper<'a, 'b> { }; match *value { - UntypedExpr::Call { - location: call_location, - fun, - arguments, - } => { + UntypedExpr::Call { fun, arguments, .. } => { let mut new_arguments = Vec::new(); new_arguments.extend(arguments); new_arguments.push(CallArg { - location: assign_location, + location: call_location, label: None, value: UntypedExpr::lambda(name, continuation, call_location), }); @@ -1776,7 +1796,6 @@ impl<'a, 'b> ExprTyper<'a, 'b> { // Now, whether this leads to an invalid call usage, that's not *our* immediate // problem. UntypedExpr::Fn { - location: call_location, fn_style, ref arguments, ref return_annotation, @@ -1790,7 +1809,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { location: call_location, fun: value, arguments: vec![CallArg { - location: assign_location, + location: call_location, label: None, value: UntypedExpr::lambda(name, continuation, call_location), }], @@ -1814,12 +1833,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> { // 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: assign_location, + location: call_location, fun: value, arguments: vec![CallArg { - location: assign_location, + location: call_location, label: None, - value: UntypedExpr::lambda(name, continuation, assign_location), + value: UntypedExpr::lambda(name, continuation, call_location), }], }, }