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 { pub fn contains(&self, byte_index: usize) -> bool {
byte_index >= self.start && byte_index < self.end byte_index >= self.start && byte_index < self.end
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,27 +12,31 @@ Assignment {
name: "something", name: "something",
}, },
}, },
pattern: Constructor { patterns: [
is_record: false, (
location: 7..14, Constructor {
name: "Some", is_record: false,
arguments: [ location: 7..14,
CallArg { name: "Some",
label: None, arguments: [
location: 12..13, CallArg {
value: Var { label: None,
location: 12..13, location: 12..13,
name: "x", value: Var {
}, location: 12..13,
name: "x",
},
},
],
module: None,
constructor: (),
with_spread: false,
tipo: (),
}, },
], None,
module: None, ),
constructor: (), ],
with_spread: false,
tipo: (),
},
kind: Expect { kind: Expect {
backpassing: false, backpassing: false,
}, },
annotation: None,
} }

View File

@ -20,18 +20,22 @@ Assignment {
name: "wow", name: "wow",
}, },
}, },
pattern: Constructor { patterns: [
is_record: false, (
location: 0..29, Constructor {
name: "True", is_record: false,
arguments: [], location: 0..29,
module: None, name: "True",
constructor: (), arguments: [],
with_spread: false, module: None,
tipo: (), constructor: (),
}, with_spread: false,
tipo: (),
},
None,
),
],
kind: Expect { kind: Expect {
backpassing: false, backpassing: false,
}, },
annotation: None,
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -11,18 +11,22 @@ Assignment {
name: "foo", name: "foo",
}, },
}, },
pattern: Constructor { patterns: [
is_record: false, (
location: 0..11, Constructor {
name: "True", is_record: false,
arguments: [], location: 0..11,
module: None, name: "True",
constructor: (), arguments: [],
with_spread: false, module: None,
tipo: (), constructor: (),
}, with_spread: false,
tipo: (),
},
None,
),
],
kind: Expect { kind: Expect {
backpassing: false, backpassing: false,
}, },
annotation: None,
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,14 +22,18 @@ Module {
], ],
preferred_format: Utf8String, preferred_format: Utf8String,
}, },
pattern: Var { patterns: [
location: 17..18, (
name: "x", Var {
}, location: 17..18,
name: "x",
},
None,
),
],
kind: Let { kind: Let {
backpassing: false, backpassing: false,
}, },
annotation: None,
}, },
Var { Var {
location: 27..28, 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] #[test]
fn trace_if_false_ko() { fn trace_if_false_ko() {
let source_code = r#" let source_code = r#"

View File

@ -707,6 +707,19 @@ Perhaps, try the following:
with_spread: bool, 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")] #[error("I tripped over some unknown labels in a pattern or function.\n")]
#[diagnostic(code("unknown::labels"))] #[diagnostic(code("unknown::labels"))]
UnknownLabels(#[related] Vec<UnknownLabels>), UnknownLabels(#[related] Vec<UnknownLabels>),
@ -1031,6 +1044,7 @@ impl ExtraData for Error {
| Error::ValidatorImported { .. } | Error::ValidatorImported { .. }
| Error::IncorrectTestArity { .. } | Error::IncorrectTestArity { .. }
| Error::GenericLeftAtBoundary { .. } | Error::GenericLeftAtBoundary { .. }
| Error::UnexpectedMultiPatternAssignment { .. }
| Error::ValidatorMustReturnBool { .. } => None, | Error::ValidatorMustReturnBool { .. } => None,
Error::UnknownType { name, .. } Error::UnknownType { name, .. }

View File

@ -262,12 +262,17 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
UntypedExpr::Assignment { UntypedExpr::Assignment {
location, location,
pattern, patterns,
value, value,
kind, 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 { UntypedExpr::Trace {
location, location,
@ -340,7 +345,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
location, location,
value, value,
op, op,
} => self.infer_un_op(location, value, op), } => self.infer_un_op(location, *value, op),
UntypedExpr::TraceIfFalse { value, location } => { UntypedExpr::TraceIfFalse { value, location } => {
self.infer_trace_if_false(*value, location) self.infer_trace_if_false(*value, location)
@ -663,10 +668,10 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
fn infer_un_op( fn infer_un_op(
&mut self, &mut self,
location: Span, location: Span,
value: Box<UntypedExpr>, value: UntypedExpr,
op: UnOp, op: UnOp,
) -> Result<TypedExpr, Error> { ) -> Result<TypedExpr, Error> {
let value = self.infer(*value)?; let value = self.infer(value)?;
let tipo = match op { let tipo = match op {
UnOp::Not => bool(), UnOp::Not => bool(),
@ -949,19 +954,20 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
)? )?
} else { } else {
if value_is_data && !untyped_pattern.is_var() && !untyped_pattern.is_discard() { 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 { return Err(Error::CastDataNoAnn {
location, location,
value: UntypedExpr::Assignment { value: UntypedExpr::Assignment {
location, location,
value: untyped_value.into(), value: untyped_value.into(),
pattern: untyped_pattern, patterns: Vec1::new((untyped_pattern, Some(ann))),
kind, 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(), name: "...".to_string(),
location: Span::empty(), location: Span::empty(),
}), }),
pattern: untyped_pattern, patterns: Vec1::new((untyped_pattern, None)),
kind: AssignmentKind::Let { backpassing: true }, kind: AssignmentKind::Let { backpassing: true },
annotation: None,
} }
} }
_ => UntypedExpr::Assignment { _ => UntypedExpr::Assignment {
location: Span::empty(), location: Span::empty(),
value: Box::new(untyped_value), value: Box::new(untyped_value),
pattern: untyped_pattern, patterns: Vec1::new((untyped_pattern, None)),
kind: AssignmentKind::let_(), kind: AssignmentKind::let_(),
annotation: None,
}, },
}, },
}); });
@ -1721,17 +1725,20 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
PipeTyper::infer(self, expressions) PipeTyper::infer(self, expressions)
} }
fn backpass(&mut self, breakpoint: UntypedExpr, continuation: Vec<UntypedExpr>) -> UntypedExpr { fn backpass(
let (value, pattern, annotation, kind, assign_location) = match breakpoint { &mut self,
UntypedExpr::Assignment { breakpoint: UntypedExpr,
location, mut continuation: Vec<UntypedExpr>,
value, ) -> UntypedExpr {
pattern, let UntypedExpr::Assignment {
annotation, location: assign_location,
kind, value,
.. kind,
} => (value, pattern, annotation, kind, location), patterns,
_ => unreachable!("backpass misuse: breakpoint isn't an Assignment ?!"), ..
} = breakpoint
else {
unreachable!("backpass misuse: breakpoint isn't an Assignment ?!");
}; };
let value_location = value.location(); let value_location = value.location();
@ -1744,33 +1751,41 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
.unwrap_or_else(|| value_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 let mut names = Vec::new();
// in front of the continuation sequence. This is because we do not support patterns in function argument
// (which is perhaps something we should support?). for (index, (pattern, annotation)) in patterns.into_iter().enumerate() {
let (name, continuation) = match pattern { // In case where we have a Pattern that isn't simply a let-binding to a name, we do insert an extra let-binding
Pattern::Var { name, .. } | Pattern::Discard { name, .. } if kind.is_let() => { // in front of the continuation sequence. This is because we do not support patterns in function argument
(name.clone(), continuation) // (which is perhaps something we should support?).
match pattern {
Pattern::Var { name, .. } | Pattern::Discard { name, .. } if kind.is_let() => {
names.push(name.clone());
}
_ => {
let name = format!("{}_{}", ast::BACKPASS_VARIABLE, index);
continuation.insert(
0,
UntypedExpr::Assignment {
location: assign_location,
value: UntypedExpr::Var {
location: value_location,
name: name.clone(),
}
.into(),
patterns: Vec1::new((pattern, annotation)),
// erase backpassing while preserving assignment kind.
kind: match kind {
AssignmentKind::Let { .. } => AssignmentKind::let_(),
AssignmentKind::Expect { .. } => AssignmentKind::expect(),
},
},
);
names.push(name);
}
} }
_ => { }
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 { match *value {
UntypedExpr::Call { fun, arguments, .. } => { UntypedExpr::Call { fun, arguments, .. } => {
@ -1779,7 +1794,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
new_arguments.push(CallArg { new_arguments.push(CallArg {
location: call_location, location: call_location,
label: None, label: None,
value: UntypedExpr::lambda(name, continuation, call_location), value: UntypedExpr::lambda(names, continuation, call_location),
}); });
UntypedExpr::Call { UntypedExpr::Call {
@ -1811,7 +1826,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
arguments: vec![CallArg { arguments: vec![CallArg {
location: call_location, location: call_location,
label: None, 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 { arguments: vec![CallArg {
location: call_location, location: call_location,
label: None, 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() => { UntypedExpr::Assignment { kind, .. } if kind.is_backpassing() => {
breakpoint = Some(expression); 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), _ => prefix.push(expression),
} }
} }
} }
if let Some(breakpoint) = breakpoint { if let Some(breakpoint) = breakpoint {
prefix.push(self.backpass(breakpoint, suffix)); prefix.push(self.backpass(breakpoint, suffix));
return self.infer_seq(location, prefix); return self.infer_seq(location, prefix);
@ -2149,9 +2182,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
sample: UntypedExpr::Assignment { sample: UntypedExpr::Assignment {
location: Span::empty(), location: Span::empty(),
value: Box::new(subject.clone()), value: Box::new(subject.clone()),
pattern: clauses[0].patterns[0].clone(), patterns: Vec1::new((clauses[0].patterns[0].clone(), None)),
kind: AssignmentKind::let_(), kind: AssignmentKind::let_(),
annotation: None,
}, },
}); });
} }