diff --git a/CHANGELOG.md b/CHANGELOG.md index 13639317..f3a83613 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - **uplc**: uplc `Constant::Data` formatting - **aiken-lang**: empty records properly parse as record sugar - **aiken-lang**: escape sequences are now properly preserved after formatting +- **aiken-lang**: fixed parser ambiguity when using record constructor in if conditions followed by single-line var expressions #735 - **aiken-project**: when a module name has a hyphen we should behave like rust and force an underscore ## v1.0.16-alpha - 2023-08-24 diff --git a/crates/aiken-lang/src/parser/expr/block.rs b/crates/aiken-lang/src/parser/expr/block.rs index f40424e7..1d4634a9 100644 --- a/crates/aiken-lang/src/parser/expr/block.rs +++ b/crates/aiken-lang/src/parser/expr/block.rs @@ -24,15 +24,24 @@ mod tests { use crate::assert_expr; #[test] - fn block_basic() { + fn block_let() { assert_expr!( r#" let b = { let x = 4 - x + 5 } "# ); } + + #[test] + fn block_single() { + assert_expr!( + r#"{ + foo + } + "# + ); + } } diff --git a/crates/aiken-lang/src/parser/expr/if_else.rs b/crates/aiken-lang/src/parser/expr/if_else.rs index 3d9c5deb..9a90ee7d 100644 --- a/crates/aiken-lang/src/parser/expr/if_else.rs +++ b/crates/aiken-lang/src/parser/expr/if_else.rs @@ -71,4 +71,19 @@ mod tests { "# ); } + + #[test] + fn if_else_ambiguous_record() { + assert_expr!( + r#" + if ec1 == Infinity { + ec2 + } else if ec1 == Foo { foo } { + ec1 + } else { + Infinity + } + "# + ); + } } diff --git a/crates/aiken-lang/src/parser/expr/record.rs b/crates/aiken-lang/src/parser/expr/record.rs index e8473a12..5cc4cc25 100644 --- a/crates/aiken-lang/src/parser/expr/record.rs +++ b/crates/aiken-lang/src/parser/expr/record.rs @@ -72,6 +72,38 @@ pub fn parser( }, ), )) + // NOTE: There's an ambiguity when the record shorthand syntax is used + // from within an if-else statement in the case of single-variable if-branch. + // + // For example, imagine the following: + // + // ``` + // if season == Summer { + // foo + // } else { + // bar + // } + // ``` + // + // Without that next odd parser combinator, the parser would parse: + // + // ``` + // if season == Summer { foo } + // else { + // bar + // } + // ``` + // + // And immediately choke on the next `else` because the if-branch body has + // already been consumed and interpreted as a record definition. So the next + // combinator ensures that we give priority back to an if-then statement rather + // than to the record definition. + .then_ignore( + just(Token::RightBrace) + .ignore_then(just(Token::Else)) + .not() + .rewind(), + ) .map(|(value, name)| ast::CallArg { location: value.location(), value, diff --git a/crates/aiken-lang/src/parser/expr/snapshots/block_let.snap b/crates/aiken-lang/src/parser/expr/snapshots/block_let.snap new file mode 100644 index 00000000..cea26d50 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/block_let.snap @@ -0,0 +1,49 @@ +--- +source: crates/aiken-lang/src/parser/expr/block.rs +description: "Code:\n\nlet b = {\n let x = 4\n x + 5\n}\n" +--- +Assignment { + location: 0..31, + value: Sequence { + location: 12..29, + expressions: [ + Assignment { + location: 12..21, + value: UInt { + location: 20..21, + value: "4", + base: Decimal { + numeric_underscore: false, + }, + }, + pattern: Var { + location: 16..17, + name: "x", + }, + kind: Let, + annotation: None, + }, + BinOp { + location: 24..29, + name: AddInt, + left: Var { + location: 24..25, + name: "x", + }, + right: UInt { + location: 28..29, + value: "5", + base: Decimal { + numeric_underscore: false, + }, + }, + }, + ], + }, + pattern: Var { + location: 4..5, + name: "b", + }, + kind: Let, + annotation: None, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/block_single.snap b/crates/aiken-lang/src/parser/expr/snapshots/block_single.snap new file mode 100644 index 00000000..8123fcd3 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/block_single.snap @@ -0,0 +1,8 @@ +--- +source: crates/aiken-lang/src/parser/expr/block.rs +description: "Code:\n\n{\nfoo\n}\n" +--- +Var { + location: 2..5, + name: "foo", +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/if_else_ambiguous_record.snap b/crates/aiken-lang/src/parser/expr/snapshots/if_else_ambiguous_record.snap new file mode 100644 index 00000000..64551469 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/if_else_ambiguous_record.snap @@ -0,0 +1,66 @@ +--- +source: crates/aiken-lang/src/parser/expr/if_else.rs +description: "Code:\n\nif ec1 == Infinity {\n ec2\n} else if ec1 == Foo { foo } {\n ec1\n} else {\n Infinity\n}\n" +--- +If { + location: 0..85, + branches: [ + IfBranch { + condition: BinOp { + location: 3..18, + name: Eq, + left: Var { + location: 3..6, + name: "ec1", + }, + right: Var { + location: 10..18, + name: "Infinity", + }, + }, + body: Var { + location: 23..26, + name: "ec2", + }, + location: 3..28, + }, + IfBranch { + condition: BinOp { + location: 37..55, + name: Eq, + left: Var { + location: 37..40, + name: "ec1", + }, + right: Call { + arguments: [ + CallArg { + label: Some( + "foo", + ), + location: 50..53, + value: Var { + location: 50..53, + name: "foo", + }, + }, + ], + fun: Var { + location: 44..47, + name: "Foo", + }, + location: 44..55, + }, + }, + body: Var { + location: 60..63, + name: "ec1", + }, + location: 37..65, + }, + ], + final_else: Var { + location: 75..83, + name: "Infinity", + }, +}