Do not allow empty continuation for backpassed assignments
Fixes #1111. Signed-off-by: KtorZ <matthias.benkort@gmail.com>
This commit is contained in:
parent
b8bd91a589
commit
222d244bcf
|
@ -33,6 +33,7 @@ fn check_module(
|
|||
|
||||
for (package, module) in extra {
|
||||
let mut warnings = vec![];
|
||||
|
||||
let typed_module = module
|
||||
.infer(
|
||||
&id_gen,
|
||||
|
@ -3309,6 +3310,28 @@ fn softcasting_unused_let_binding() {
|
|||
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]
|
||||
fn dangling_trace_let_standalone() {
|
||||
let source_code = r#"
|
||||
|
|
|
@ -1970,11 +1970,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
PipeTyper::infer(self, expressions)
|
||||
}
|
||||
|
||||
#[allow(clippy::result_large_err)]
|
||||
fn backpass(
|
||||
&mut self,
|
||||
breakpoint: UntypedExpr,
|
||||
mut continuation: Vec<UntypedExpr>,
|
||||
) -> UntypedExpr {
|
||||
) -> Result<UntypedExpr, Error> {
|
||||
let UntypedExpr::Assignment {
|
||||
location,
|
||||
value,
|
||||
|
@ -1985,6 +1986,15 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
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 call_location = Span {
|
||||
|
@ -2101,11 +2111,11 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
value: UntypedExpr::lambda(names, continuation, lambda_span),
|
||||
});
|
||||
|
||||
UntypedExpr::Call {
|
||||
Ok(UntypedExpr::Call {
|
||||
location: call_location,
|
||||
fun,
|
||||
arguments: new_arguments,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 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() {
|
||||
call
|
||||
Ok(call)
|
||||
} else {
|
||||
UntypedExpr::Fn {
|
||||
Ok(UntypedExpr::Fn {
|
||||
location: call_location,
|
||||
fn_style,
|
||||
arguments,
|
||||
body: call.into(),
|
||||
return_annotation,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2152,7 +2162,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
// 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 {
|
||||
_ => Ok(UntypedExpr::Call {
|
||||
location: call_location,
|
||||
fun: value,
|
||||
arguments: vec![CallArg {
|
||||
|
@ -2160,7 +2170,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
label: None,
|
||||
value: UntypedExpr::lambda(names, continuation, lambda_span),
|
||||
}],
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2203,7 +2213,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
}
|
||||
|
||||
if let Some(breakpoint) = breakpoint {
|
||||
prefix.push(self.backpass(breakpoint, suffix));
|
||||
prefix.push(self.backpass(breakpoint, suffix)?);
|
||||
return self.infer_seq(location, prefix);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue