Merge pull request #871 from aiken-lang/backpassing

Backpassing
This commit is contained in:
Matthias Benkort 2024-03-11 01:06:17 +01:00 committed by GitHub
commit f02b9b0f0c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 608 additions and 109 deletions

View File

@ -19,6 +19,7 @@
- **aiken-lang**: Strings can contain a nul byte using the escape sequence `\0`. @KtorZ - **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 `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**: 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 ### Fixed

View File

@ -16,6 +16,7 @@ use std::{
use uplc::machine::runtime::Compressable; use uplc::machine::runtime::Compressable;
use vec1::Vec1; use vec1::Vec1;
pub const BACKPASS_VARIABLE: &str = "_backpass";
pub const CAPTURE_VARIABLE: &str = "_capture"; pub const CAPTURE_VARIABLE: &str = "_capture";
pub const PIPE_VARIABLE: &str = "_pipe"; pub const PIPE_VARIABLE: &str = "_pipe";
@ -792,6 +793,19 @@ impl<A> Arg<A> {
self.arg_name.get_variable_name() 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) { pub fn put_doc(&mut self, new_doc: String) {
self.doc = Some(new_doc); self.doc = Some(new_doc);
} }
@ -1422,25 +1436,59 @@ impl Default for Bls12_381Point {
} }
} }
pub type UntypedAssignmentKind = AssignmentKind<bool>;
pub type TypedAssignmentKind = AssignmentKind<()>;
#[derive(Debug, Clone, PartialEq, Eq, Copy, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Copy, serde::Serialize, serde::Deserialize)]
pub enum AssignmentKind { pub enum AssignmentKind<T> {
Let, Let { backpassing: T },
Expect, Expect { backpassing: T },
} }
impl AssignmentKind { impl From<UntypedAssignmentKind> for TypedAssignmentKind {
fn from(kind: UntypedAssignmentKind) -> TypedAssignmentKind {
match kind {
AssignmentKind::Let { .. } => AssignmentKind::Let { backpassing: () },
AssignmentKind::Expect { .. } => AssignmentKind::Expect { backpassing: () },
}
}
}
impl<T> AssignmentKind<T> {
pub fn is_let(&self) -> bool { pub fn is_let(&self) -> bool {
matches!(self, AssignmentKind::Let) matches!(self, AssignmentKind::Let { .. })
} }
pub fn is_expect(&self) -> bool { pub fn is_expect(&self) -> bool {
matches!(self, AssignmentKind::Expect) matches!(self, AssignmentKind::Expect { .. })
} }
pub fn location_offset(&self) -> usize { pub fn location_offset(&self) -> usize {
match self { match self {
AssignmentKind::Let => 3, AssignmentKind::Let { .. } => 3,
AssignmentKind::Expect => 6, AssignmentKind::Expect { .. } => 6,
}
}
}
impl AssignmentKind<bool> {
pub fn is_backpassing(&self) -> bool {
match self {
Self::Let { backpassing } | Self::Expect { backpassing } => *backpassing,
}
}
}
impl<T: Default> AssignmentKind<T> {
pub fn let_() -> Self {
AssignmentKind::Let {
backpassing: Default::default(),
}
}
pub fn expect() -> Self {
AssignmentKind::Expect {
backpassing: Default::default(),
} }
} }
} }

View File

@ -1,10 +1,10 @@
use crate::{ use crate::{
ast::{ ast::{
self, Annotation, Arg, AssignmentKind, BinOp, Bls12_381Point, ByteArrayFormatPreference, self, Annotation, Arg, ArgName, BinOp, Bls12_381Point, ByteArrayFormatPreference, CallArg,
CallArg, Curve, DataType, DataTypeKey, DefinitionLocation, IfBranch, Located, Curve, DataType, DataTypeKey, DefinitionLocation, IfBranch, Located, LogicalOpChainKind,
LogicalOpChainKind, ParsedCallArg, Pattern, RecordConstructorArg, RecordUpdateSpread, Span, ParsedCallArg, Pattern, RecordConstructorArg, RecordUpdateSpread, Span, TraceKind,
TraceKind, TypedClause, TypedDataType, TypedRecordUpdateArg, UnOp, UntypedClause, TypedAssignmentKind, TypedClause, TypedDataType, TypedRecordUpdateArg, UnOp,
UntypedRecordUpdateArg, UntypedAssignmentKind, UntypedClause, UntypedRecordUpdateArg,
}, },
builtins::void, builtins::void,
parser::token::Base, parser::token::Base,
@ -106,7 +106,7 @@ pub enum TypedExpr {
tipo: Rc<Type>, tipo: Rc<Type>,
value: Box<Self>, value: Box<Self>,
pattern: Pattern<PatternConstructor, Rc<Type>>, pattern: Pattern<PatternConstructor, Rc<Type>>,
kind: AssignmentKind, kind: TypedAssignmentKind,
}, },
Trace { Trace {
@ -519,7 +519,7 @@ pub enum UntypedExpr {
location: Span, location: Span,
value: Box<Self>, value: Box<Self>,
pattern: Pattern<(), ()>, pattern: Pattern<(), ()>,
kind: AssignmentKind, kind: UntypedAssignmentKind,
annotation: Option<Annotation>, annotation: Option<Annotation>,
}, },
@ -1299,4 +1299,29 @@ impl UntypedExpr {
Self::String { .. } | Self::UInt { .. } | Self::ByteArray { .. } Self::String { .. } | Self::UInt { .. } | Self::ByteArray { .. }
) )
} }
pub fn lambda(name: String, expressions: Vec<UntypedExpr>, 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,
}
}
} }

View File

@ -4,9 +4,9 @@ use crate::{
CallArg, ClauseGuard, Constant, CurveType, DataType, Definition, Function, IfBranch, CallArg, ClauseGuard, Constant, CurveType, DataType, Definition, Function, IfBranch,
LogicalOpChainKind, ModuleConstant, Pattern, RecordConstructor, RecordConstructorArg, LogicalOpChainKind, ModuleConstant, Pattern, RecordConstructor, RecordConstructorArg,
RecordUpdateSpread, Span, TraceKind, TypeAlias, TypedArg, UnOp, UnqualifiedImport, RecordUpdateSpread, Span, TraceKind, TypeAlias, TypedArg, UnOp, UnqualifiedImport,
UntypedArg, UntypedArgVia, UntypedClause, UntypedClauseGuard, UntypedDefinition, UntypedArg, UntypedArgVia, UntypedAssignmentKind, UntypedClause, UntypedClauseGuard,
UntypedFunction, UntypedModule, UntypedPattern, UntypedRecordUpdateArg, Use, Validator, UntypedDefinition, UntypedFunction, UntypedModule, UntypedPattern, UntypedRecordUpdateArg,
CAPTURE_VARIABLE, Use, Validator, CAPTURE_VARIABLE,
}, },
docvec, docvec,
expr::{FnStyle, UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR}, expr::{FnStyle, UntypedExpr, DEFAULT_ERROR_STR, DEFAULT_TODO_STR},
@ -681,14 +681,16 @@ impl<'comments> Formatter<'comments> {
&mut self, &mut self,
pattern: &'a UntypedPattern, pattern: &'a UntypedPattern,
value: &'a UntypedExpr, value: &'a UntypedExpr,
kind: AssignmentKind, kind: UntypedAssignmentKind,
annotation: &'a Option<Annotation>, annotation: &'a Option<Annotation>,
) -> Document<'a> { ) -> Document<'a> {
let keyword = match kind { let keyword = match kind {
AssignmentKind::Let => "let", AssignmentKind::Let { .. } => "let",
AssignmentKind::Expect => "expect", AssignmentKind::Expect { .. } => "expect",
}; };
let symbol = if kind.is_backpassing() { "<-" } else { "=" };
match pattern { match pattern {
UntypedPattern::Constructor { UntypedPattern::Constructor {
name, module: None, .. name, module: None, ..
@ -708,7 +710,8 @@ impl<'comments> Formatter<'comments> {
.to_doc() .to_doc()
.append(" ") .append(" ")
.append(pattern.append(annotation).group()) .append(pattern.append(annotation).group())
.append(" =") .append(" ")
.append(symbol)
.append(self.case_clause_value(value)) .append(self.case_clause_value(value))
} }
} }

View File

@ -559,7 +559,7 @@ impl<'a> CodeGenerator<'a> {
&subject_type, &subject_type,
AssignmentProperties { AssignmentProperties {
value_type: subject.tipo(), value_type: subject.tipo(),
kind: AssignmentKind::Let, kind: AssignmentKind::let_(),
remove_unused: false, remove_unused: false,
full_check: false, full_check: false,
msg_func: None, msg_func: None,
@ -843,7 +843,7 @@ impl<'a> CodeGenerator<'a> {
) -> AirTree { ) -> AirTree {
assert!( assert!(
match &value { match &value {
AirTree::Var { name, .. } if props.kind == AssignmentKind::Let => { AirTree::Var { name, .. } if props.kind.is_let() => {
name != "_" name != "_"
} }
_ => true, _ => true,
@ -2812,7 +2812,7 @@ impl<'a> CodeGenerator<'a> {
&actual_type, &actual_type,
AssignmentProperties { AssignmentProperties {
value_type: data(), value_type: data(),
kind: AssignmentKind::Expect, kind: AssignmentKind::expect(),
remove_unused: false, remove_unused: false,
full_check: true, full_check: true,
msg_func, msg_func,

View File

@ -4,8 +4,8 @@ use super::{
}; };
use crate::{ use crate::{
ast::{ ast::{
AssignmentKind, BinOp, ClauseGuard, Constant, DataTypeKey, FunctionAccessKey, Pattern, BinOp, ClauseGuard, Constant, DataTypeKey, FunctionAccessKey, Pattern, Span, TraceLevel,
Span, TraceLevel, TypedArg, TypedClause, TypedClauseGuard, TypedDataType, TypedPattern, TypedArg, TypedAssignmentKind, TypedClause, TypedClauseGuard, TypedDataType, TypedPattern,
UnOp, UnOp,
}, },
builtins::{bool, data, function, int, list, void}, builtins::{bool, data, function, int, list, void},
@ -68,7 +68,7 @@ pub enum HoistableFunction {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct AssignmentProperties { pub struct AssignmentProperties {
pub value_type: Rc<Type>, pub value_type: Rc<Type>,
pub kind: AssignmentKind, pub kind: TypedAssignmentKind,
pub remove_unused: bool, pub remove_unused: bool,
pub full_check: bool, pub full_check: bool,
pub msg_func: Option<AirMsg>, pub msg_func: Option<AirMsg>,

View File

@ -24,7 +24,9 @@ Test(
with_spread: false, with_spread: false,
tipo: (), tipo: (),
}, },
kind: Expect, kind: Expect {
backpassing: false,
},
annotation: None, annotation: None,
}, },
Var { Var {

View File

@ -29,7 +29,9 @@ Fn(
location: 17..18, location: 17..18,
name: "x", name: "x",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
doc: None, doc: None,

View File

@ -1,10 +1,9 @@
use chumsky::prelude::*;
use crate::{ use crate::{
ast, ast,
expr::UntypedExpr, expr::UntypedExpr,
parser::{annotation, error::ParseError, pattern, token::Token}, parser::{annotation, error::ParseError, pattern, token::Token},
}; };
use chumsky::prelude::*;
pub fn let_( pub fn let_(
r: Recursive<'_, Token, UntypedExpr, ParseError>, r: Recursive<'_, Token, UntypedExpr, ParseError>,
@ -12,9 +11,9 @@ pub fn let_(
just(Token::Let) just(Token::Let)
.ignore_then(pattern()) .ignore_then(pattern())
.then(just(Token::Colon).ignore_then(annotation()).or_not()) .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()) .then(r.clone())
.validate(move |((pattern, annotation), value), span, emit| { .validate(move |(((pattern, annotation), 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))
} }
@ -23,7 +22,9 @@ pub fn let_(
location: span, location: span,
value: Box::new(value), value: Box::new(value),
pattern, pattern,
kind: ast::AssignmentKind::Let, kind: ast::AssignmentKind::Let {
backpassing: kind == Token::LArrow,
},
annotation, annotation,
} }
}) })
@ -36,24 +37,27 @@ 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())
.then_ignore(just(Token::Equal)) .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) = opt_pattern.unwrap_or_else(|| { let ((pattern, annotation), kind) = opt_pattern.unwrap_or_else(|| {
( (
ast::UntypedPattern::Constructor { (
is_record: false, ast::UntypedPattern::Constructor {
location: span, is_record: false,
name: "True".to_string(), location: span,
arguments: vec![], name: "True".to_string(),
module: None, arguments: vec![],
constructor: (), module: None,
with_spread: false, constructor: (),
tipo: (), with_spread: false,
}, tipo: (),
None, },
None,
),
Token::Equal,
) )
}); });
@ -65,7 +69,9 @@ pub fn expect(
location: span, location: span,
value: Box::new(value), value: Box::new(value),
pattern, pattern,
kind: ast::AssignmentKind::Expect, kind: ast::AssignmentKind::Expect {
backpassing: kind == Token::LArrow,
},
annotation, annotation,
} }
}) })

View File

@ -20,7 +20,9 @@ Assignment {
location: 16..17, location: 16..17,
name: "x", name: "x",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
BinOp { BinOp {
@ -44,6 +46,8 @@ Assignment {
location: 4..5, location: 4..5,
name: "b", name: "b",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
} }

View File

@ -31,6 +31,8 @@ Assignment {
with_spread: false, with_spread: false,
tipo: (), tipo: (),
}, },
kind: Expect, kind: Expect {
backpassing: false,
},
annotation: None, annotation: None,
} }

View File

@ -30,6 +30,8 @@ Assignment {
with_spread: false, with_spread: false,
tipo: (), tipo: (),
}, },
kind: Expect, kind: Expect {
backpassing: false,
},
annotation: None, annotation: None,
} }

View File

@ -20,7 +20,9 @@ Assignment {
location: 13..14, location: 13..14,
name: "a", name: "a",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
], ],
@ -35,6 +37,8 @@ Assignment {
with_spread: false, with_spread: false,
tipo: (), tipo: (),
}, },
kind: Expect, kind: Expect {
backpassing: false,
},
annotation: None, annotation: None,
} }

View File

@ -20,7 +20,9 @@ Assignment {
location: 14..15, location: 14..15,
name: "b", name: "b",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
], ],
@ -29,6 +31,8 @@ Assignment {
location: 4..5, location: 4..5,
name: "a", name: "a",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
} }

View File

@ -20,7 +20,9 @@ Assignment {
location: 14..15, location: 14..15,
name: "b", name: "b",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
], ],
@ -29,6 +31,8 @@ Assignment {
location: 4..5, location: 4..5,
name: "a", name: "a",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
} }

View File

@ -20,7 +20,9 @@ Assignment {
location: 16..17, location: 16..17,
name: "b", name: "b",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
Var { Var {
@ -33,6 +35,8 @@ Assignment {
location: 4..5, location: 4..5,
name: "a", name: "a",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
} }

View File

@ -21,6 +21,8 @@ Assignment {
with_spread: false, with_spread: false,
tipo: (), tipo: (),
}, },
kind: Expect, kind: Expect {
backpassing: false,
},
annotation: None, annotation: None,
} }

View File

@ -31,7 +31,9 @@ Sequence {
location: 4..5, location: 4..5,
name: "x", name: "x",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
Assignment { Assignment {
@ -115,7 +117,9 @@ Sequence {
location: 24..33, location: 24..33,
name: "map_add_x", name: "map_add_x",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
Call { Call {

View File

@ -18,7 +18,9 @@ Sequence {
location: 8..9, location: 8..9,
name: "i", name: "i",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
Assignment { Assignment {
@ -34,7 +36,9 @@ Sequence {
location: 28..29, location: 28..29,
name: "j", name: "j",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
Assignment { Assignment {
@ -54,7 +58,9 @@ Sequence {
location: 48..49, location: 48..49,
name: "k", name: "k",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
], ],

View File

@ -32,6 +32,8 @@ Assignment {
location: 4..9, location: 4..9,
name: "thing", name: "thing",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
} }

View File

@ -44,7 +44,9 @@ Sequence {
location: 4..9, location: 4..9,
name: "tuple", name: "tuple",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
BinOp { BinOp {

View File

@ -31,7 +31,9 @@ Sequence {
location: 4..5, location: 4..5,
name: "a", name: "a",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
Tuple { Tuple {

View File

@ -89,7 +89,9 @@ When {
location: 55..62, location: 55..62,
name: "amazing", name: "amazing",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
Var { Var {

View File

@ -163,6 +163,8 @@ pub fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = ParseError> {
just("!=").to(Token::NotEqual), just("!=").to(Token::NotEqual),
just('!').to(Token::Bang), just('!').to(Token::Bang),
just('?').to(Token::Question), just('?').to(Token::Question),
just("<-").to(Token::LArrow),
just("->").to(Token::RArrow),
choice(( choice((
just("<=").to(Token::LessEqual), just("<=").to(Token::LessEqual),
just('<').to(Token::Less), just('<').to(Token::Less),
@ -170,7 +172,6 @@ pub fn lexer() -> impl Parser<char, Vec<(Token, Span)>, Error = ParseError> {
just('>').to(Token::Greater), just('>').to(Token::Greater),
)), )),
just('+').to(Token::Plus), just('+').to(Token::Plus),
just("->").to(Token::RArrow),
just('-').to(Token::Minus), just('-').to(Token::Minus),
just('*').to(Token::Star), just('*').to(Token::Star),
just('/').to(Token::Slash), just('/').to(Token::Slash),

View File

@ -62,6 +62,7 @@ pub enum Token {
Pipe, // '|>' Pipe, // '|>'
Dot, // '.' Dot, // '.'
RArrow, // '->' RArrow, // '->'
LArrow, // '<-'
DotDot, // '..' DotDot, // '..'
EndOfFile, EndOfFile,
// Docs/Extra // Docs/Extra
@ -152,6 +153,7 @@ impl fmt::Display for Token {
Token::Pipe => "|>", Token::Pipe => "|>",
Token::Dot => ".", Token::Dot => ".",
Token::RArrow => "->", Token::RArrow => "->",
Token::LArrow => "<-",
Token::DotDot => "..", Token::DotDot => "..",
Token::EndOfFile => "EOF", Token::EndOfFile => "EOF",
Token::Comment => "//", Token::Comment => "//",

View File

@ -23,7 +23,9 @@ Module {
location: 19..20, location: 19..20,
name: "a", name: "a",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
UInt { UInt {
@ -61,7 +63,9 @@ Module {
location: 56..57, location: 56..57,
name: "a", name: "a",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
UInt { UInt {
@ -110,7 +114,9 @@ Module {
location: 93..94, location: 93..94,
name: "a", name: "a",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
doc: None, doc: None,
@ -155,7 +161,9 @@ Module {
location: 126..127, location: 126..127,
name: "a", name: "a",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
BinOp { BinOp {

View File

@ -28,7 +28,9 @@ Module {
location: 17..18, location: 17..18,
name: "x", name: "x",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
Var { Var {

View File

@ -26,7 +26,9 @@ Module {
location: 17..18, location: 17..18,
name: "x", name: "x",
}, },
kind: Let, kind: Let {
backpassing: false,
},
annotation: None, annotation: None,
}, },
Var { Var {

View File

@ -1185,6 +1185,204 @@ fn trace_if_false_ok() {
assert!(check(parse(source_code)).is_ok()) assert!(check(parse(source_code)).is_ok())
} }
#[test]
fn backpassing_basic() {
let source_code = r#"
fn and_then(opt: Option<a>, then: fn(a) -> Option<b>) -> Option<b> {
when opt is {
None -> None
Some(a) -> then(a)
}
}
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
let i <- and_then(opt_i)
let j <- and_then(opt_j)
Some(i + j)
}
"#;
assert!(check(parse(source_code)).is_ok())
}
#[test]
fn backpassing_expect_simple() {
let source_code = r#"
fn and_then(opt: Option<a>, then: fn(a) -> Option<b>) -> Option<b> {
when opt is {
None -> None
Some(a) -> then(a)
}
}
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
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<a>, then: fn(Option<a>) -> Option<b>) -> Option<b> {
when opt is {
None -> None
Some(a) -> then(Some(a))
}
}
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
expect Some(i) <- and_then(opt_i)
expect Some(j) <- and_then(opt_j)
Some(i + j)
}
"#;
assert!(check(parse(source_code)).is_ok())
}
#[test]
fn backpassing_interleaved_capture() {
let source_code = r#"
fn and_then(opt: Option<a>, then: fn(a) -> Option<b>) -> Option<b> {
when opt is {
None -> None
Some(a) -> then(a)
}
}
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
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<a>, then: fn(a) -> Option<b>) -> Option<b> {
when opt is {
None -> None
Some(a) -> then(a)
}
}
type Foo {
foo: Int,
}
fn backpassing(opt_i: Option<Foo>, opt_j: Option<Foo>) -> Option<Int> {
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<a>, then: fn(a) -> Option<b>) -> Option<b> {
when opt is {
None -> None
Some(a) -> then(a)
}
}
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
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<a>, then: fn(a) -> Option<b>) -> Option<b> {
when opt is {
None -> None
Some(a) -> then(a)
}
}
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
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<a>, then: fn(a) -> Option<b>) -> Option<b> {
when opt is {
None -> None
Some(a) -> then(a)
}
}
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
let i <- and_then
let j <- and_then(opt_j)
Some(i + j)
}
"#;
assert!(matches!(
check(parse(source_code)),
Err((_, Error::IncorrectFieldsArity { .. }))
))
}
#[test]
fn backpassing_expect_type_mismatch() {
let source_code = r#"
fn and_then(opt: Option<a>, then: fn(a) -> Option<b>) -> Option<b> {
when opt is {
None -> None
Some(a) -> then(a)
}
}
fn backpassing(opt_i: Option<Int>, opt_j: Option<Int>) -> Option<Int> {
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] #[test]
fn trace_if_false_ko() { fn trace_if_false_ko() {
let source_code = r#" let source_code = r#"

View File

@ -8,12 +8,12 @@ use super::{
}; };
use crate::{ use crate::{
ast::{ ast::{
Annotation, Arg, ArgName, AssignmentKind, BinOp, Bls12_381Point, ByteArrayFormatPreference, self, Annotation, Arg, ArgName, AssignmentKind, BinOp, Bls12_381Point,
CallArg, ClauseGuard, Constant, Curve, IfBranch, LogicalOpChainKind, Pattern, ByteArrayFormatPreference, CallArg, ClauseGuard, Constant, Curve, IfBranch,
RecordUpdateSpread, Span, TraceKind, TraceLevel, Tracing, TypedArg, TypedCallArg, LogicalOpChainKind, Pattern, RecordUpdateSpread, Span, TraceKind, TraceLevel, Tracing,
TypedClause, TypedClauseGuard, TypedIfBranch, TypedPattern, TypedRecordUpdateArg, UnOp, TypedArg, TypedCallArg, TypedClause, TypedClauseGuard, TypedIfBranch, TypedPattern,
UntypedArg, UntypedClause, UntypedClauseGuard, UntypedIfBranch, UntypedPattern, TypedRecordUpdateArg, UnOp, UntypedArg, UntypedAssignmentKind, UntypedClause,
UntypedRecordUpdateArg, UntypedClauseGuard, UntypedIfBranch, UntypedPattern, UntypedRecordUpdateArg,
}, },
builtins::{ builtins::{
bool, byte_array, function, g1_element, g2_element, int, list, string, tuple, void, 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, &mut self,
untyped_pattern: UntypedPattern, untyped_pattern: UntypedPattern,
untyped_value: UntypedExpr, untyped_value: UntypedExpr,
kind: AssignmentKind, kind: UntypedAssignmentKind,
annotation: &Option<Annotation>, annotation: &Option<Annotation>,
location: Span, location: Span,
) -> Result<TypedExpr, Error> { ) -> Result<TypedExpr, Error> {
@ -978,12 +978,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
// If `expect` is explicitly used, we still check exhaustiveness but instead of returning an // 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. // error we emit a warning which explains that using `expect` is unnecessary.
match kind { match kind {
AssignmentKind::Let => { AssignmentKind::Let { .. } => {
self.environment self.environment
.check_exhaustiveness(&[&pattern], location, true)? .check_exhaustiveness(&[&pattern], location, true)?
} }
AssignmentKind::Expect => { AssignmentKind::Expect { .. } => {
let is_exaustive_pattern = self let is_exaustive_pattern = self
.environment .environment
.check_exhaustiveness(&[&pattern], location, false) .check_exhaustiveness(&[&pattern], location, false)
@ -999,12 +999,26 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
}, },
pattern_location: untyped_pattern.location(), pattern_location: untyped_pattern.location(),
value_location: untyped_value.location(), value_location: untyped_value.location(),
sample: UntypedExpr::Assignment { sample: match untyped_value {
location: Span::empty(), UntypedExpr::Var { name, .. } if name == ast::BACKPASS_VARIABLE => {
value: Box::new(untyped_value), UntypedExpr::Assignment {
pattern: untyped_pattern, location: Span::empty(),
kind: AssignmentKind::Let, value: Box::new(UntypedExpr::Var {
annotation: None, name: "...".to_string(),
location: Span::empty(),
}),
pattern: untyped_pattern,
kind: AssignmentKind::Let { backpassing: true },
annotation: None,
}
}
_ => UntypedExpr::Assignment {
location: Span::empty(),
value: Box::new(untyped_value),
pattern: untyped_pattern,
kind: AssignmentKind::let_(),
annotation: None,
},
}, },
}); });
} }
@ -1014,7 +1028,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
Ok(TypedExpr::Assignment { Ok(TypedExpr::Assignment {
location, location,
tipo: value_typ, tipo: value_typ,
kind, kind: kind.into(),
pattern, pattern,
value: Box::new(typed_value), value: Box::new(typed_value),
}) })
@ -1707,13 +1721,157 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
PipeTyper::infer(self, expressions) 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,
value,
pattern,
annotation,
kind,
..
} => (value, pattern, annotation, kind, location),
_ => unreachable!("backpass misuse: breakpoint isn't an Assignment ?!"),
};
let value_location = value.location();
let call_location = Span {
start: value_location.end,
end: continuation
.last()
.map(|expr| expr.location().end)
.unwrap_or_else(|| value_location.end),
};
// In case where we have a Pattern that isn't simply a let-binding to a name, we do insert an extra let-binding
// in front of the continuation sequence. This is because we do not support patterns in function argument
// (which is perhaps something we should support?).
let (name, continuation) = match pattern {
Pattern::Var { name, .. } | Pattern::Discard { name, .. } if kind.is_let() => {
(name.clone(), continuation)
}
_ => {
let mut with_assignment = vec![UntypedExpr::Assignment {
location: assign_location,
value: UntypedExpr::Var {
location: value_location,
name: ast::BACKPASS_VARIABLE.to_string(),
}
.into(),
pattern,
// Erase backpassing while preserving assignment kind.
kind: match kind {
AssignmentKind::Let { .. } => AssignmentKind::let_(),
AssignmentKind::Expect { .. } => AssignmentKind::expect(),
},
annotation,
}];
with_assignment.extend(continuation);
(ast::BACKPASS_VARIABLE.to_string(), with_assignment)
}
};
match *value {
UntypedExpr::Call { fun, arguments, .. } => {
let mut new_arguments = Vec::new();
new_arguments.extend(arguments);
new_arguments.push(CallArg {
location: call_location,
label: None,
value: UntypedExpr::lambda(name, continuation, call_location),
});
UntypedExpr::Call {
location: call_location,
fun,
arguments: new_arguments,
}
}
// This typically occurs on function captures. We do not try to assert anything on the
// length of the arguments here. We defer that to the rest of the type-checker. The
// only thing we have to do is rewrite the AST as-if someone had passed a callback.
//
// Now, whether this leads to an invalid call usage, that's not *our* immediate
// problem.
UntypedExpr::Fn {
fn_style,
ref arguments,
ref return_annotation,
..
} => {
let return_annotation = return_annotation.clone();
let arguments = arguments.iter().skip(1).cloned().collect::<Vec<_>>();
let call = UntypedExpr::Call {
location: call_location,
fun: value,
arguments: vec![CallArg {
location: call_location,
label: None,
value: UntypedExpr::lambda(name, continuation, call_location),
}],
};
if arguments.is_empty() {
call
} else {
UntypedExpr::Fn {
location: call_location,
fn_style,
arguments,
body: call.into(),
return_annotation,
}
}
}
// Similarly to function captures, if we have any other expression we simply call it
// with our continuation. If the expression isn't callable? No problem, the
// type-checker will catch that eventually in exactly the same way as if the code was
// written like that to begin with.
_ => UntypedExpr::Call {
location: call_location,
fun: value,
arguments: vec![CallArg {
location: call_location,
label: None,
value: UntypedExpr::lambda(name, continuation, call_location),
}],
},
}
}
fn infer_seq(&mut self, location: Span, untyped: Vec<UntypedExpr>) -> Result<TypedExpr, Error> { fn infer_seq(&mut self, location: Span, untyped: Vec<UntypedExpr>) -> Result<TypedExpr, Error> {
// Search for backpassing.
let mut breakpoint = None;
let mut prefix = Vec::with_capacity(untyped.len());
let mut suffix = Vec::with_capacity(untyped.len());
for expression in untyped.into_iter() {
if breakpoint.is_some() {
suffix.push(expression);
} else {
match expression {
UntypedExpr::Assignment { kind, .. } if kind.is_backpassing() => {
breakpoint = Some(expression);
}
_ => prefix.push(expression),
}
}
}
if let Some(breakpoint) = breakpoint {
prefix.push(self.backpass(breakpoint, suffix));
return self.infer_seq(location, prefix);
}
let sequence = self.in_new_scope(|scope| { let sequence = self.in_new_scope(|scope| {
let count = untyped.len(); let count = prefix.len();
let mut expressions = Vec::with_capacity(count); let mut expressions = Vec::with_capacity(count);
for (i, expression) in untyped.into_iter().enumerate() { for (i, expression) in prefix.into_iter().enumerate() {
let no_assignment = assert_no_assignment(&expression); let no_assignment = assert_no_assignment(&expression);
let typed_expression = scope.infer(expression)?; let typed_expression = scope.infer(expression)?;
@ -1992,7 +2150,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
location: Span::empty(), location: Span::empty(),
value: Box::new(subject.clone()), value: Box::new(subject.clone()),
pattern: clauses[0].patterns[0].clone(), pattern: clauses[0].patterns[0].clone(),
kind: AssignmentKind::Let, kind: AssignmentKind::let_(),
annotation: None, annotation: None,
}, },
}); });
@ -2120,7 +2278,7 @@ fn assert_assignment(expr: TypedExpr) -> Result<TypedExpr, Error> {
with_spread: false, with_spread: false,
tipo: void(), tipo: void(),
}, },
kind: AssignmentKind::Let, kind: AssignmentKind::let_(),
}); });
} }

View File

@ -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::{ use super::{
error::{Error, UnifyErrorSituation}, error::{Error, UnifyErrorSituation},
expr::ExprTyper, expr::ExprTyper,
Type, ValueConstructor, ValueConstructorVariant, 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)] #[derive(Debug)]
pub(crate) struct PipeTyper<'a, 'b, 'c> { pub(crate) struct PipeTyper<'a, 'b, 'c> {
@ -184,7 +181,7 @@ impl<'a, 'b, 'c> PipeTyper<'a, 'b, 'c> {
let assignment = TypedExpr::Assignment { let assignment = TypedExpr::Assignment {
location, location,
tipo: expression.tipo(), tipo: expression.tipo(),
kind: AssignmentKind::Let, kind: AssignmentKind::let_(),
value: Box::new(expression), value: Box::new(expression),
pattern: Pattern::Var { pattern: Pattern::Var {
location, location,