feat(backpassing): implements multi patterns

The main trick here was transforming Assignment
to contain `Vec<UntypedPattern, Option<Annotation>>`
in a field called patterns. This then meant that I
could remove the `pattern` and `annotation` field
from `Assignment`. The parser handles `=` and `<-`
just fine because in the future `=` with multi
patterns will mean some kind of optimization on tuples.
But, since we don't have that optimization yet, when
someone uses multi patterns with an `=` there will be an
error returned from the type checker right where `infer_seq`
looks for `backpassing`. From there the rest of the work
was in `Project::backpassing` where I only needed to rework
some things to work with a list of patterns instead of just one.
This commit is contained in:
rvcas 2024-03-11 18:23:17 -04:00 committed by Lucas
parent f02b9b0f0c
commit b6b52ba508
26 changed files with 592 additions and 292 deletions

View File

@ -1782,6 +1782,47 @@ impl Span {
}
}
/// Map the current start and end of the Span to new values.
///
/// # Examples
///
/// ```
/// use aiken_lang::ast::Span;
///
/// let span = Span { start: 0, end: 1 };
///
/// let other = span.map(|start, end| (start + 2, end + 4));
///
/// assert_eq!(other.start, 2);
/// assert_eq!(other.end, 5);
/// ```
pub fn map<F: FnOnce(usize, usize) -> (usize, usize)>(&self, f: F) -> Self {
let (start, end) = f(self.start, self.end);
Self { start, end }
}
/// Map the current end of the Span to a new value.
///
/// # Examples
///
/// ```
/// use aiken_lang::ast::Span;
///
/// let span = Span { start: 0, end: 1 };
///
/// let other = span.map_end(|end| end + 1);
///
/// assert_eq!(other.start, 0);
/// assert_eq!(other.end, 2);
/// ```
pub fn map_end<F: FnOnce(usize) -> usize>(&self, f: F) -> Self {
Self {
start: self.start,
end: f(self.end),
}
}
pub fn contains(&self, byte_index: usize) -> bool {
byte_index >= self.start && byte_index < self.end
}

View File

@ -4,7 +4,7 @@ use crate::{
Curve, DataType, DataTypeKey, DefinitionLocation, IfBranch, Located, LogicalOpChainKind,
ParsedCallArg, Pattern, RecordConstructorArg, RecordUpdateSpread, Span, TraceKind,
TypedAssignmentKind, TypedClause, TypedDataType, TypedRecordUpdateArg, UnOp,
UntypedAssignmentKind, UntypedClause, UntypedRecordUpdateArg,
UntypedAssignmentKind, UntypedClause, UntypedPattern, UntypedRecordUpdateArg,
},
builtins::void,
parser::token::Base,
@ -518,9 +518,8 @@ pub enum UntypedExpr {
Assignment {
location: Span,
value: Box<Self>,
pattern: Pattern<(), ()>,
patterns: Vec1<(UntypedPattern, Option<Annotation>)>,
kind: UntypedAssignmentKind,
annotation: Option<Annotation>,
},
Trace {
@ -1300,11 +1299,13 @@ impl UntypedExpr {
)
}
pub fn lambda(name: String, expressions: Vec<UntypedExpr>, location: Span) -> Self {
pub fn lambda(names: Vec<String>, expressions: Vec<UntypedExpr>, location: Span) -> Self {
Self::Fn {
location,
fn_style: FnStyle::Plain,
arguments: vec![Arg {
arguments: names
.into_iter()
.map(|name| Arg {
location,
doc: None,
annotation: None,
@ -1315,7 +1316,8 @@ impl UntypedExpr {
location,
is_validator_param: false,
},
}],
})
.collect(),
body: Self::Sequence {
location,
expressions,

View File

@ -679,10 +679,9 @@ impl<'comments> Formatter<'comments> {
fn assignment<'a>(
&mut self,
pattern: &'a UntypedPattern,
patterns: &'a Vec1<(UntypedPattern, Option<Annotation>)>,
value: &'a UntypedExpr,
kind: UntypedAssignmentKind,
annotation: &'a Option<Annotation>,
) -> Document<'a> {
let keyword = match kind {
AssignmentKind::Let { .. } => "let",
@ -691,13 +690,21 @@ impl<'comments> Formatter<'comments> {
let symbol = if kind.is_backpassing() { "<-" } else { "=" };
match pattern {
match patterns.first() {
(
UntypedPattern::Constructor {
name, module: None, ..
} if name == "True" && annotation.is_none() && kind.is_expect() => {
},
annotation,
) if name == "True"
&& annotation.is_none()
&& kind.is_expect()
&& patterns.len() == 1 =>
{
keyword.to_doc().append(self.case_clause_value(value))
}
_ => {
let patterns = patterns.into_iter().map(|(pattern, annotation)| {
self.pop_empty_lines(pattern.location().end);
let pattern = self.pattern(pattern);
@ -706,11 +713,16 @@ impl<'comments> Formatter<'comments> {
.as_ref()
.map(|a| ": ".to_doc().append(self.annotation(a)));
pattern.append(annotation).group()
});
keyword
.to_doc()
.append(" ")
.append(pattern.append(annotation).group())
.append(" ")
.append(break_("", " "))
.append(join(patterns, break_(",", ", ")))
.nest(INDENT)
.append(break_(",", ""))
.append(break_("", " "))
.append(symbol)
.append(self.case_clause_value(value))
}
@ -907,11 +919,10 @@ impl<'comments> Formatter<'comments> {
UntypedExpr::Assignment {
value,
pattern,
annotation,
patterns,
kind,
..
} => self.assignment(pattern, value, *kind, annotation),
} => self.assignment(patterns, value, *kind),
UntypedExpr::Trace {
kind, text, then, ..

View File

@ -14,7 +14,9 @@ Test(
location: 45..50,
name: "False",
},
pattern: Constructor {
patterns: [
(
Constructor {
is_record: false,
location: 38..42,
name: "True",
@ -24,10 +26,12 @@ Test(
with_spread: false,
tipo: (),
},
None,
),
],
kind: Expect {
backpassing: false,
},
annotation: None,
},
Var {
location: 54..59,

View File

@ -25,14 +25,18 @@ Fn(
},
},
},
pattern: Var {
patterns: [
(
Var {
location: 17..18,
name: "x",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
doc: None,
location: 0..8,

View File

@ -9,23 +9,30 @@ pub fn let_(
r: Recursive<'_, Token, UntypedExpr, ParseError>,
) -> impl Parser<Token, UntypedExpr, Error = ParseError> + '_ {
just(Token::Let)
.ignore_then(pattern())
.ignore_then(
pattern()
.then(just(Token::Colon).ignore_then(annotation()).or_not())
.separated_by(just(Token::Comma))
.at_least(1),
)
.then(choice((just(Token::Equal), just(Token::LArrow))))
.then(r.clone())
.validate(move |(((pattern, annotation), kind), value), span, emit| {
.validate(move |((patterns, kind), value), span, emit| {
if matches!(value, UntypedExpr::Assignment { .. }) {
emit(ParseError::invalid_assignment_right_hand_side(span))
}
let patterns = patterns
.try_into()
.expect("We use at_least(1) so this should never be empty");
UntypedExpr::Assignment {
location: span,
value: Box::new(value),
pattern,
patterns,
kind: ast::AssignmentKind::Let {
backpassing: kind == Token::LArrow,
},
annotation,
}
})
}
@ -37,14 +44,20 @@ pub fn expect(
.ignore_then(
pattern()
.then(just(Token::Colon).ignore_then(annotation()).or_not())
.separated_by(just(Token::Comma))
.at_least(1)
.then(choice((just(Token::Equal), just(Token::LArrow))))
.or_not(),
)
.then(r.clone())
.validate(move |(opt_pattern, value), span, emit| {
let ((pattern, annotation), kind) = opt_pattern.unwrap_or_else(|| {
(
if matches!(value, UntypedExpr::Assignment { .. }) {
emit(ParseError::invalid_assignment_right_hand_side(span))
}
let (patterns, kind) = opt_pattern.unwrap_or_else(|| {
(
vec![(
ast::UntypedPattern::Constructor {
is_record: false,
location: span,
@ -56,23 +69,22 @@ pub fn expect(
tipo: (),
},
None,
),
)],
Token::Equal,
)
});
if matches!(value, UntypedExpr::Assignment { .. }) {
emit(ParseError::invalid_assignment_right_hand_side(span))
}
let patterns = patterns
.try_into()
.expect("We use at_least(1) so this should never be empty");
UntypedExpr::Assignment {
location: span,
patterns,
value: Box::new(value),
pattern,
kind: ast::AssignmentKind::Expect {
backpassing: kind == Token::LArrow,
},
annotation,
}
})
}

View File

@ -16,14 +16,18 @@ Assignment {
numeric_underscore: false,
},
},
pattern: Var {
patterns: [
(
Var {
location: 16..17,
name: "x",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
BinOp {
location: 24..29,
@ -42,12 +46,16 @@ Assignment {
},
],
},
pattern: Var {
patterns: [
(
Var {
location: 4..5,
name: "b",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
}

View File

@ -12,7 +12,9 @@ Assignment {
name: "something",
},
},
pattern: Constructor {
patterns: [
(
Constructor {
is_record: false,
location: 7..14,
name: "Some",
@ -31,8 +33,10 @@ Assignment {
with_spread: false,
tipo: (),
},
None,
),
],
kind: Expect {
backpassing: false,
},
annotation: None,
}

View File

@ -20,7 +20,9 @@ Assignment {
name: "wow",
},
},
pattern: Constructor {
patterns: [
(
Constructor {
is_record: false,
location: 0..29,
name: "True",
@ -30,8 +32,10 @@ Assignment {
with_spread: false,
tipo: (),
},
None,
),
],
kind: Expect {
backpassing: false,
},
annotation: None,
}

View File

@ -16,18 +16,24 @@ Assignment {
numeric_underscore: false,
},
},
pattern: Var {
patterns: [
(
Var {
location: 13..14,
name: "a",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
],
},
pattern: Constructor {
patterns: [
(
Constructor {
is_record: false,
location: 0..21,
name: "True",
@ -37,8 +43,10 @@ Assignment {
with_spread: false,
tipo: (),
},
None,
),
],
kind: Expect {
backpassing: false,
},
annotation: None,
}

View File

@ -16,23 +16,31 @@ Assignment {
numeric_underscore: false,
},
},
pattern: Var {
patterns: [
(
Var {
location: 14..15,
name: "b",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
],
},
pattern: Var {
patterns: [
(
Var {
location: 4..5,
name: "a",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
}

View File

@ -16,23 +16,31 @@ Assignment {
numeric_underscore: false,
},
},
pattern: Var {
patterns: [
(
Var {
location: 14..15,
name: "b",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
],
},
pattern: Var {
patterns: [
(
Var {
location: 4..5,
name: "a",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
}

View File

@ -16,14 +16,18 @@ Assignment {
numeric_underscore: false,
},
},
pattern: Var {
patterns: [
(
Var {
location: 16..17,
name: "b",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
Var {
location: 25..26,
@ -31,12 +35,16 @@ Assignment {
},
],
},
pattern: Var {
patterns: [
(
Var {
location: 4..5,
name: "a",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
}

View File

@ -11,7 +11,9 @@ Assignment {
name: "foo",
},
},
pattern: Constructor {
patterns: [
(
Constructor {
is_record: false,
location: 0..11,
name: "True",
@ -21,8 +23,10 @@ Assignment {
with_spread: false,
tipo: (),
},
None,
),
],
kind: Expect {
backpassing: false,
},
annotation: None,
}

View File

@ -27,14 +27,18 @@ Sequence {
},
location: 8..18,
},
pattern: Var {
patterns: [
(
Var {
location: 4..5,
name: "x",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
Assignment {
location: 20..65,
@ -113,14 +117,18 @@ Sequence {
},
return_annotation: None,
},
pattern: Var {
patterns: [
(
Var {
location: 24..33,
name: "map_add_x",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
Call {
arguments: [

View File

@ -14,14 +14,18 @@ Sequence {
numeric_underscore: true,
},
},
pattern: Var {
patterns: [
(
Var {
location: 8..9,
name: "i",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
Assignment {
location: 24..41,
@ -32,14 +36,18 @@ Sequence {
numeric_underscore: true,
},
},
pattern: Var {
patterns: [
(
Var {
location: 28..29,
name: "j",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
Assignment {
location: 44..59,
@ -54,14 +62,18 @@ Sequence {
},
},
},
pattern: Var {
patterns: [
(
Var {
location: 48..49,
name: "k",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
],
}

View File

@ -28,12 +28,16 @@ Assignment {
],
tail: None,
},
pattern: Var {
patterns: [
(
Var {
location: 4..9,
name: "thing",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
}

View File

@ -40,14 +40,18 @@ Sequence {
},
],
},
pattern: Var {
patterns: [
(
Var {
location: 4..9,
name: "tuple",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
BinOp {
location: 25..70,

View File

@ -27,14 +27,18 @@ Sequence {
},
location: 8..15,
},
pattern: Var {
patterns: [
(
Var {
location: 4..5,
name: "a",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
Tuple {
location: 16..23,

View File

@ -85,14 +85,18 @@ When {
numeric_underscore: false,
},
},
pattern: Var {
patterns: [
(
Var {
location: 55..62,
name: "amazing",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
Var {
location: 71..78,

View File

@ -19,14 +19,18 @@ Module {
location: 23..26,
name: "bar",
},
pattern: Var {
patterns: [
(
Var {
location: 19..20,
name: "a",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
UInt {
location: 30..32,
@ -59,14 +63,18 @@ Module {
location: 60..63,
name: "bar",
},
pattern: Var {
patterns: [
(
Var {
location: 56..57,
name: "a",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
UInt {
location: 67..69,
@ -110,14 +118,18 @@ Module {
},
},
},
pattern: Var {
patterns: [
(
Var {
location: 93..94,
name: "a",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
doc: None,
location: 74..84,
@ -157,14 +169,18 @@ Module {
},
location: 130..137,
},
pattern: Var {
patterns: [
(
Var {
location: 126..127,
name: "a",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
BinOp {
location: 141..153,

View File

@ -24,14 +24,18 @@ Module {
],
preferred_format: Utf8String,
},
pattern: Var {
patterns: [
(
Var {
location: 17..18,
name: "x",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
Var {
location: 29..30,

View File

@ -22,14 +22,18 @@ Module {
],
preferred_format: Utf8String,
},
pattern: Var {
patterns: [
(
Var {
location: 17..18,
name: "x",
},
None,
),
],
kind: Let {
backpassing: false,
},
annotation: None,
},
Var {
location: 27..28,

View File

@ -1383,6 +1383,74 @@ fn backpassing_expect_type_mismatch() {
))
}
#[test]
fn backpassing_multi_args() {
let source_code = r#"
fn fold(list: List<a>, init: b, then: fn(a, b) -> b) -> b {
when list is {
[] -> init
[x, ..rest] -> fold(rest, then(x, init), then)
}
}
fn backpassing() -> Int {
let elem, acc <- fold([1, 2, 3], 0)
elem + acc
}
"#;
assert!(check(parse(source_code)).is_ok())
}
#[test]
fn backpassing_multi_args_expect() {
let source_code = r#"
pub type Bar {
Foo(Int)
Wow(Int)
}
fn fold(list: List<a>, init: b, then: fn(a, b) -> b) -> b {
when list is {
[] -> init
[x, ..rest] -> fold(rest, then(x, init), then)
}
}
pub fn backpassing() -> Bar {
expect Foo(elem), Wow(acc) <- fold([Foo(1), Foo(2), Foo(3)], Wow(0))
Wow(elem + acc)
}
"#;
assert!(matches!(check(parse(source_code)), Ok((warnings, _)) if warnings.is_empty()))
}
#[test]
fn backpassing_multi_args_using_equals() {
let source_code = r#"
fn fold(list: List<a>, init: b, then: fn(a, b) -> b) -> b {
when list is {
[] -> init
[x, ..rest] -> fold(rest, then(x, init), then)
}
}
fn backpassing() -> Int {
let elem, acc = fold([1, 2, 3], 0, fn(elem, acc) { elem + acc })
elem + acc
}
"#;
assert!(matches!(
check(parse(source_code)),
Err((_, Error::UnexpectedMultiPatternAssignment { .. }))
))
}
#[test]
fn trace_if_false_ko() {
let source_code = r#"

View File

@ -707,6 +707,19 @@ Perhaps, try the following:
with_spread: bool,
},
#[error("I discovered a regular let assignment with multiple patterns.\n")]
#[diagnostic(code("unexpected::multi_pattern_assignment"))]
#[diagnostic(help(
"Did you mean to use backpassing syntax with {}?",
"<-".if_supports_color(Stdout, |s| s.purple())
))]
UnexpectedMultiPatternAssignment {
#[label("unexpected")]
location: Span,
#[label("<-")]
arrow: Span,
},
#[error("I tripped over some unknown labels in a pattern or function.\n")]
#[diagnostic(code("unknown::labels"))]
UnknownLabels(#[related] Vec<UnknownLabels>),
@ -1031,6 +1044,7 @@ impl ExtraData for Error {
| Error::ValidatorImported { .. }
| Error::IncorrectTestArity { .. }
| Error::GenericLeftAtBoundary { .. }
| Error::UnexpectedMultiPatternAssignment { .. }
| Error::ValidatorMustReturnBool { .. } => None,
Error::UnknownType { name, .. }

View File

@ -262,12 +262,17 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
UntypedExpr::Assignment {
location,
pattern,
patterns,
value,
kind,
annotation,
..
} => self.infer_assignment(pattern, *value, kind, &annotation, location),
} => {
// at this point due to backpassing rewrites,
// patterns is guaranteed to have one item
let (pattern, annotation) = patterns.into_vec().swap_remove(0);
self.infer_assignment(pattern, *value, kind, &annotation, location)
}
UntypedExpr::Trace {
location,
@ -340,7 +345,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
location,
value,
op,
} => self.infer_un_op(location, value, op),
} => self.infer_un_op(location, *value, op),
UntypedExpr::TraceIfFalse { value, location } => {
self.infer_trace_if_false(*value, location)
@ -663,10 +668,10 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
fn infer_un_op(
&mut self,
location: Span,
value: Box<UntypedExpr>,
value: UntypedExpr,
op: UnOp,
) -> Result<TypedExpr, Error> {
let value = self.infer(*value)?;
let value = self.infer(value)?;
let tipo = match op {
UnOp::Not => bool(),
@ -949,19 +954,20 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
)?
} else {
if value_is_data && !untyped_pattern.is_var() && !untyped_pattern.is_discard() {
let ann = Annotation::Constructor {
location: Span::empty(),
module: None,
name: "Type".to_string(),
arguments: vec![],
};
return Err(Error::CastDataNoAnn {
location,
value: UntypedExpr::Assignment {
location,
value: untyped_value.into(),
pattern: untyped_pattern,
patterns: Vec1::new((untyped_pattern, Some(ann))),
kind,
annotation: Some(Annotation::Constructor {
location: Span::empty(),
module: None,
name: "Type".to_string(),
arguments: vec![],
}),
},
});
}
@ -1007,17 +1013,15 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
name: "...".to_string(),
location: Span::empty(),
}),
pattern: untyped_pattern,
patterns: Vec1::new((untyped_pattern, None)),
kind: AssignmentKind::Let { backpassing: true },
annotation: None,
}
}
_ => UntypedExpr::Assignment {
location: Span::empty(),
value: Box::new(untyped_value),
pattern: untyped_pattern,
patterns: Vec1::new((untyped_pattern, None)),
kind: AssignmentKind::let_(),
annotation: None,
},
},
});
@ -1721,17 +1725,20 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
PipeTyper::infer(self, expressions)
}
fn backpass(&mut self, breakpoint: UntypedExpr, continuation: Vec<UntypedExpr>) -> UntypedExpr {
let (value, pattern, annotation, kind, assign_location) = match breakpoint {
UntypedExpr::Assignment {
location,
fn backpass(
&mut self,
breakpoint: UntypedExpr,
mut continuation: Vec<UntypedExpr>,
) -> UntypedExpr {
let UntypedExpr::Assignment {
location: assign_location,
value,
pattern,
annotation,
kind,
patterns,
..
} => (value, pattern, annotation, kind, location),
_ => unreachable!("backpass misuse: breakpoint isn't an Assignment ?!"),
} = breakpoint
else {
unreachable!("backpass misuse: breakpoint isn't an Assignment ?!");
};
let value_location = value.location();
@ -1744,33 +1751,41 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
.unwrap_or_else(|| value_location.end),
};
let mut names = Vec::new();
for (index, (pattern, annotation)) in patterns.into_iter().enumerate() {
// 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 {
match pattern {
Pattern::Var { name, .. } | Pattern::Discard { name, .. } if kind.is_let() => {
(name.clone(), continuation)
names.push(name.clone());
}
_ => {
let mut with_assignment = vec![UntypedExpr::Assignment {
let name = format!("{}_{}", ast::BACKPASS_VARIABLE, index);
continuation.insert(
0,
UntypedExpr::Assignment {
location: assign_location,
value: UntypedExpr::Var {
location: value_location,
name: ast::BACKPASS_VARIABLE.to_string(),
name: name.clone(),
}
.into(),
pattern,
// Erase backpassing while preserving assignment kind.
patterns: Vec1::new((pattern, annotation)),
// 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)
},
);
names.push(name);
}
}
}
};
match *value {
UntypedExpr::Call { fun, arguments, .. } => {
@ -1779,7 +1794,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
new_arguments.push(CallArg {
location: call_location,
label: None,
value: UntypedExpr::lambda(name, continuation, call_location),
value: UntypedExpr::lambda(names, continuation, call_location),
});
UntypedExpr::Call {
@ -1811,7 +1826,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
arguments: vec![CallArg {
location: call_location,
label: None,
value: UntypedExpr::lambda(name, continuation, call_location),
value: UntypedExpr::lambda(names, continuation, call_location),
}],
};
@ -1838,7 +1853,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
arguments: vec![CallArg {
location: call_location,
label: None,
value: UntypedExpr::lambda(name, continuation, call_location),
value: UntypedExpr::lambda(names, continuation, call_location),
}],
},
}
@ -1857,10 +1872,28 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
UntypedExpr::Assignment { kind, .. } if kind.is_backpassing() => {
breakpoint = Some(expression);
}
UntypedExpr::Assignment {
patterns, location, ..
} if patterns.len() > 1 => {
return Err(Error::UnexpectedMultiPatternAssignment {
arrow: patterns
.last()
.0
.location()
.map(|_, c_end| (c_end + 1, c_end + 1)),
location: patterns[1..]
.iter()
.map(|(p, _)| p.location())
.reduce(|acc, loc| acc.union(loc))
.unwrap_or(location)
.map_end(|current| current - 1),
});
}
_ => prefix.push(expression),
}
}
}
if let Some(breakpoint) = breakpoint {
prefix.push(self.backpass(breakpoint, suffix));
return self.infer_seq(location, prefix);
@ -2149,9 +2182,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
sample: UntypedExpr::Assignment {
location: Span::empty(),
value: Box::new(subject.clone()),
pattern: clauses[0].patterns[0].clone(),
patterns: Vec1::new((clauses[0].patterns[0].clone(), None)),
kind: AssignmentKind::let_(),
annotation: None,
},
});
}