Do not allow empty continuation for backpassed assignments

Fixes #1111.

Signed-off-by: KtorZ <matthias.benkort@gmail.com>
This commit is contained in:
KtorZ 2025-03-01 16:58:41 +01:00
parent b8bd91a589
commit 222d244bcf
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
2 changed files with 42 additions and 9 deletions

View File

@ -33,6 +33,7 @@ fn check_module(
for (package, module) in extra { for (package, module) in extra {
let mut warnings = vec![]; let mut warnings = vec![];
let typed_module = module let typed_module = module
.infer( .infer(
&id_gen, &id_gen,
@ -3309,6 +3310,28 @@ fn softcasting_unused_let_binding() {
assert!(warnings.is_empty(), "should not contain any warnings"); assert!(warnings.is_empty(), "should not contain any warnings");
} }
#[test]
fn dangling_let_in_block() {
let source_code = r#"
fn for_each(xs: List<Int>, with: fn(Int) -> a) -> a {
todo
}
test foo() {
{
let _ <- for_each([1, 2, 3])
}
}
"#;
let result = check_validator(parse(source_code));
assert!(
matches!(result, Err((_, Error::LastExpressionIsAssignment { .. }))),
"{result:?}"
)
}
#[test] #[test]
fn dangling_trace_let_standalone() { fn dangling_trace_let_standalone() {
let source_code = r#" let source_code = r#"

View File

@ -1970,11 +1970,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
PipeTyper::infer(self, expressions) PipeTyper::infer(self, expressions)
} }
#[allow(clippy::result_large_err)]
fn backpass( fn backpass(
&mut self, &mut self,
breakpoint: UntypedExpr, breakpoint: UntypedExpr,
mut continuation: Vec<UntypedExpr>, mut continuation: Vec<UntypedExpr>,
) -> UntypedExpr { ) -> Result<UntypedExpr, Error> {
let UntypedExpr::Assignment { let UntypedExpr::Assignment {
location, location,
value, value,
@ -1985,6 +1986,15 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
unreachable!("backpass misuse: breakpoint isn't an Assignment ?!"); unreachable!("backpass misuse: breakpoint isn't an Assignment ?!");
}; };
if continuation.is_empty() {
return Err(Error::LastExpressionIsAssignment {
location,
expr: *value,
patterns: patterns.clone(),
kind,
});
}
let value_location = value.location(); let value_location = value.location();
let call_location = Span { let call_location = Span {
@ -2101,11 +2111,11 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
value: UntypedExpr::lambda(names, continuation, lambda_span), value: UntypedExpr::lambda(names, continuation, lambda_span),
}); });
UntypedExpr::Call { Ok(UntypedExpr::Call {
location: call_location, location: call_location,
fun, fun,
arguments: new_arguments, arguments: new_arguments,
} })
} }
// This typically occurs on function captures. We do not try to assert anything on the // This typically occurs on function captures. We do not try to assert anything on the
@ -2136,15 +2146,15 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
}; };
if arguments.is_empty() { if arguments.is_empty() {
call Ok(call)
} else { } else {
UntypedExpr::Fn { Ok(UntypedExpr::Fn {
location: call_location, location: call_location,
fn_style, fn_style,
arguments, arguments,
body: call.into(), body: call.into(),
return_annotation, return_annotation,
} })
} }
} }
@ -2152,7 +2162,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
// with our continuation. If the expression isn't callable? No problem, the // 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 // type-checker will catch that eventually in exactly the same way as if the code was
// written like that to begin with. // written like that to begin with.
_ => UntypedExpr::Call { _ => Ok(UntypedExpr::Call {
location: call_location, location: call_location,
fun: value, fun: value,
arguments: vec![CallArg { arguments: vec![CallArg {
@ -2160,7 +2170,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
label: None, label: None,
value: UntypedExpr::lambda(names, continuation, lambda_span), value: UntypedExpr::lambda(names, continuation, lambda_span),
}], }],
}, }),
} }
} }
@ -2203,7 +2213,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
} }
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);
} }