diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f91db35..0999f2f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ ### Fixed - **aiken-lang**: Fix flat encoding and decoding of large integer values. @KtorZ +- **aiken-lang**: Traces should not have following expressions formatted into a block. @rvcas +- **aiken-lang**: Sequences should not be erased if the sole expression is an assignment. @rvcas +- **aiken-lang**: Should not be able to assign an assignment to an assignment. @rvcas +- **aiken-lang**: Should not be able to have an assignment in a logical op chain. @rvcas - **aiken**: Ensures that test expected to fail that return `False` are considered to pass & improve error reporting when they fail. @KtorZ ### Removed diff --git a/crates/aiken-lang/src/parser/definition/function.rs b/crates/aiken-lang/src/parser/definition/function.rs index 639f187a..92d8af38 100644 --- a/crates/aiken-lang/src/parser/definition/function.rs +++ b/crates/aiken-lang/src/parser/definition/function.rs @@ -109,4 +109,15 @@ mod tests { "# ); } + + #[test] + fn function_assignment_only() { + assert_definition!( + r#" + fn run() { + let x = 1 + 1 + } + "# + ); + } } diff --git a/crates/aiken-lang/src/parser/definition/snapshots/function_assignment_only.snap b/crates/aiken-lang/src/parser/definition/snapshots/function_assignment_only.snap new file mode 100644 index 00000000..23f7613e --- /dev/null +++ b/crates/aiken-lang/src/parser/definition/snapshots/function_assignment_only.snap @@ -0,0 +1,44 @@ +--- +source: crates/aiken-lang/src/parser/definition/function.rs +description: "Code:\n\nfn run() {\n let x = 1 + 1\n}\n" +--- +Fn( + Function { + arguments: [], + body: Assignment { + location: 13..26, + value: BinOp { + location: 21..26, + name: AddInt, + left: UInt { + location: 21..22, + value: "1", + base: Decimal { + numeric_underscore: false, + }, + }, + right: UInt { + location: 25..26, + value: "1", + base: Decimal { + numeric_underscore: false, + }, + }, + }, + pattern: Var { + location: 17..18, + name: "x", + }, + kind: Let, + annotation: None, + }, + doc: None, + location: 0..8, + name: "run", + public: false, + return_annotation: None, + return_type: (), + end_position: 27, + can_error: true, + }, +) diff --git a/crates/aiken-lang/src/parser/error.rs b/crates/aiken-lang/src/parser/error.rs index 01be9ca2..d51217ff 100644 --- a/crates/aiken-lang/src/parser/error.rs +++ b/crates/aiken-lang/src/parser/error.rs @@ -28,6 +28,16 @@ impl ParseError { self } + pub fn invalid_assignment_right_hand_side(span: Span) -> Self { + Self { + kind: ErrorKind::InvalidAssignmentRightHandSide, + span, + while_parsing: None, + expected: HashSet::new(), + label: Some("invalid assignment right hand side"), + } + } + pub fn invalid_tuple_index(span: Span, index: String, suffix: Option) -> Self { let hint = suffix.map(|suffix| format!("Did you mean '{index}{suffix}'?")); Self { @@ -163,6 +173,16 @@ pub enum ErrorKind { hint: Option, }, + #[error("I discovered an invalid assignment.")] + #[diagnostic( + help( + "{} and {} bindings must be followed by a valid expression.", + "let".if_supports_color(Stdout, |s| s.yellow()), + "expect".if_supports_color(Stdout, |s| s.yellow()), + ), + )] + InvalidAssignmentRightHandSide, + #[error("I tripped over a {}", fmt_curve_type(.curve))] PointNotOnCurve { curve: CurveType }, diff --git a/crates/aiken-lang/src/parser/expr/assignment.rs b/crates/aiken-lang/src/parser/expr/assignment.rs index 7525c569..2ebc6887 100644 --- a/crates/aiken-lang/src/parser/expr/assignment.rs +++ b/crates/aiken-lang/src/parser/expr/assignment.rs @@ -14,15 +14,19 @@ pub fn let_( .then(just(Token::Colon).ignore_then(annotation()).or_not()) .then_ignore(just(Token::Equal)) .then(r.clone()) - .map_with_span( - move |((pattern, annotation), value), span| UntypedExpr::Assignment { + .validate(move |((pattern, annotation), value), span, emit| { + if matches!(value, UntypedExpr::Assignment { .. }) { + emit(ParseError::invalid_assignment_right_hand_side(span)) + } + + UntypedExpr::Assignment { location: span, value: Box::new(value), pattern, kind: ast::AssignmentKind::Let, annotation, - }, - ) + } + }) } pub fn expect( @@ -36,7 +40,7 @@ pub fn expect( .or_not(), ) .then(r.clone()) - .map_with_span(move |(opt_pattern, value), span| { + .validate(move |(opt_pattern, value), span, emit| { let (pattern, annotation) = opt_pattern.unwrap_or_else(|| { ( ast::UntypedPattern::Constructor { @@ -53,6 +57,10 @@ pub fn expect( ) }); + if matches!(value, UntypedExpr::Assignment { .. }) { + emit(ParseError::invalid_assignment_right_hand_side(span)) + } + UntypedExpr::Assignment { location: span, value: Box::new(value), diff --git a/crates/aiken-lang/src/parser/expr/block.rs b/crates/aiken-lang/src/parser/expr/block.rs index 1d4634a9..370536f8 100644 --- a/crates/aiken-lang/src/parser/expr/block.rs +++ b/crates/aiken-lang/src/parser/expr/block.rs @@ -11,7 +11,17 @@ pub fn parser( choice(( sequence .clone() - .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)), + .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)) + .map_with_span(|e, span| { + if matches!(e, UntypedExpr::Assignment { .. }) { + UntypedExpr::Sequence { + location: span, + expressions: vec![e], + } + } else { + e + } + }), sequence.clone().delimited_by( choice((just(Token::LeftParen), just(Token::NewLineLeftParen))), just(Token::RightParen), diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 693b8257..a3dde5d1 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -1650,6 +1650,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> { let mut typed_expressions = vec![]; for expression in expressions { + assert_no_assignment(&expression)?; + let typed_expression = self.infer(expression)?; self.unify(