From 5737556efce9777c44ab27bc4106a85e9dcc4b56 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Tue, 1 Oct 2024 12:24:31 +0200 Subject: [PATCH] Fix compiler crash around dangling expect/let in traces Fixes #1029. --- CHANGELOG.md | 1 + crates/aiken-lang/src/expr.rs | 22 ++++++++ crates/aiken-lang/src/tests/check.rs | 47 +++++++++++++++++ crates/aiken-lang/src/tipo/expr.rs | 34 +++++------- crates/aiken-project/src/tests/gen_uplc.rs | 60 ++++++++++++++++++++++ 5 files changed, 142 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 538d6483..b382f868 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Changed +- **aiken-lang**: Fix compiler crash on trace + expect as last expression of a clause. See #1029. @KtorZ - **uplc**: Fix (again :grimacing:) cost-models for PlutusV1 & PlutusV2. @MicroProofs ### Removed diff --git a/crates/aiken-lang/src/expr.rs b/crates/aiken-lang/src/expr.rs index 29d118bd..548a401b 100644 --- a/crates/aiken-lang/src/expr.rs +++ b/crates/aiken-lang/src/expr.rs @@ -203,6 +203,28 @@ impl From> for Vec1 { } impl TypedExpr { + pub fn and_then(self, next: Self) -> Self { + if let TypedExpr::Trace { + tipo, + location, + then, + text, + } = self + { + return TypedExpr::Trace { + tipo, + location, + then: Box::new(then.and_then(next)), + text, + }; + } + + TypedExpr::Sequence { + location: self.location(), + expressions: vec![self, next], + } + } + pub fn sequence(exprs: &[TypedExpr]) -> Self { TypedExpr::Sequence { location: Span::empty(), diff --git a/crates/aiken-lang/src/tests/check.rs b/crates/aiken-lang/src/tests/check.rs index f338e9f7..d51ae517 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -3295,3 +3295,50 @@ fn softcasting_unused_let_binding() { let (warnings, _) = result.unwrap(); assert!(warnings.is_empty(), "should not contain any warnings"); } + +#[test] +fn dangling_trace_let_standalone() { + let source_code = r#" + test foo() { + trace @"foo" + let True = True + } + "#; + + assert!(matches!( + check_validator(parse(source_code)), + Err((_, Error::LastExpressionIsAssignment { .. })) + )) +} + +#[test] +fn dangling_trace_let_in_sequence() { + let source_code = r#" + test foo() { + let predicate = True + trace @"foo" + let result = predicate + } + "#; + + assert!(matches!( + check_validator(parse(source_code)), + Err((_, Error::LastExpressionIsAssignment { .. })) + )) +} + +#[test] +fn dangling_trace_let_in_trace() { + let source_code = r#" + test foo() { + trace @"foo" + trace @"bar" + let result = True + } + "#; + + assert!(matches!( + check_validator(parse(source_code)), + Err((_, Error::LastExpressionIsAssignment { .. })) + )) +} diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index fca09223..dfd3558c 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -1565,10 +1565,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { let then = if let Some(filler) = recover_from_no_assignment(assert_no_assignment(&then), then.location())? { - TypedExpr::Sequence { - location, - expressions: vec![scope.infer(then)?, filler], - } + scope.infer(then)?.and_then(filler) } else { scope.infer(then)? }; @@ -1638,10 +1635,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { let typed_final_else = if let Some(filler) = recover_from_no_assignment(assert_no_assignment(&final_else), final_else.location())? { - TypedExpr::Sequence { - location: final_else.location(), - expressions: vec![self.infer(final_else)?, filler], - } + self.infer(final_else)?.and_then(filler) } else { self.infer(final_else)? }; @@ -1696,10 +1690,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { assert_no_assignment(&branch.body), branch.body.location(), )? { - TypedExpr::Sequence { - location: branch.body.location(), - expressions: vec![typer.infer(branch.body.clone())?, filler], - } + typer.infer(branch.body.clone())?.and_then(filler) } else { typer.infer(branch.body.clone())? }; @@ -1720,10 +1711,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { assert_no_assignment(&branch.body), branch.body.location(), )? { - TypedExpr::Sequence { - location: branch.body.location(), - expressions: vec![self.infer(branch.body.clone())?, filler], - } + self.infer(branch.body.clone())?.and_then(filler) } else { self.infer(branch.body.clone())? }; @@ -1815,10 +1803,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { body_infer.map_err(|e| e.with_unify_error_rigid_names(&body_rigid_names)); let body = if let Some(filler) = recover_from_no_assignment(no_assignment, location)? { - TypedExpr::Sequence { - location, - expressions: vec![inferred_body?, filler], - } + inferred_body?.and_then(filler) } else { inferred_body? }; @@ -2206,8 +2191,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> { if let Some(filler) = recover_from_no_assignment(no_assignment, typed_expression.location())? { - expressions.push(typed_expression); - expressions.push(filler); + match typed_expression.and_then(filler) { + TypedExpr::Sequence { + expressions: seq, .. + } => expressions.extend(seq), + trace => expressions.push(trace), + } } else { expressions.push(typed_expression); } @@ -2391,6 +2380,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { .collect::, Error>>()?; let then = self.infer(then)?; + let tipo = then.tipo(); if let TraceKind::Todo = kind { diff --git a/crates/aiken-project/src/tests/gen_uplc.rs b/crates/aiken-project/src/tests/gen_uplc.rs index 53670f25..2ea886ae 100644 --- a/crates/aiken-project/src/tests/gen_uplc.rs +++ b/crates/aiken-project/src/tests/gen_uplc.rs @@ -6477,3 +6477,63 @@ fn hard_soft_cast() { assert_uplc(src, program, false, false); } + +#[test] +fn dangling_trace_expect_standalone() { + let src = r#" + test foo() { + trace @"foo" + expect True + } + "#; + + let program = Term::bool(true) + .delayed_if_then_else( + Term::unit(), + Term::Error.delayed_trace(Term::string("expect True")), + ) + .delayed_trace(Term::string("foo")); + + assert_uplc(src, program, false, true) +} + +#[test] +fn dangling_trace_expect_in_sequence() { + let src = r#" + test foo() { + let predicate = True + trace @"foo" + expect predicate + } + "#; + + let program = Term::bool(true) + .delayed_if_then_else( + Term::unit(), + Term::Error.delayed_trace(Term::string("expect predicate")), + ) + .delayed_trace(Term::string("foo")); + + assert_uplc(src, program, false, true) +} + +#[test] +fn dangling_trace_expect_in_trace() { + let src = r#" + test foo() { + trace @"foo" + trace @"bar" + expect True + } + "#; + + let program = Term::bool(true) + .delayed_if_then_else( + Term::unit(), + Term::Error.delayed_trace(Term::string("expect True")), + ) + .delayed_trace(Term::string("bar")) + .delayed_trace(Term::string("foo")); + + assert_uplc(src, program, false, true) +}