Merge pull request #807 from aiken-lang/rvcas/various-fixes

Various Fixes for parsing and checking
This commit is contained in:
Matthias Benkort 2024-01-20 10:42:31 +01:00 committed by GitHub
commit 9ee2d58ba3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 376 additions and 54 deletions

View File

@ -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

View File

@ -109,4 +109,15 @@ mod tests {
"#
);
}
#[test]
fn function_assignment_only() {
assert_definition!(
r#"
fn run() {
let x = 1 + 1
}
"#
);
}
}

View File

@ -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,
},
)

View File

@ -28,6 +28,16 @@ impl ParseError {
self
}
pub fn invalid_assignment_right_hand_side(span: Span) -> Self {
Self {
kind: ErrorKind::UnfinishedAssignmentRightHandSide,
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<String>) -> Self {
let hint = suffix.map(|suffix| format!("Did you mean '{index}{suffix}'?"));
Self {
@ -163,6 +173,16 @@ pub enum ErrorKind {
hint: Option<String>,
},
#[error("I spotted an unfinished assignment.")]
#[diagnostic(
help(
"{} and {} bindings must be followed by a valid, complete, expression.",
"let".if_supports_color(Stdout, |s| s.yellow()),
"expect".if_supports_color(Stdout, |s| s.yellow()),
),
)]
UnfinishedAssignmentRightHandSide,
#[error("I tripped over a {}", fmt_curve_type(.curve))]
PointNotOnCurve { curve: CurveType },

View File

@ -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),
@ -86,4 +94,42 @@ mod tests {
fn expect_trace_if_false() {
assert_expr!("expect foo?");
}
#[test]
fn expect_unfinished_let() {
assert_expr!(
"
let a =
// foo
let b = 42
"
);
}
#[test]
fn expect_let_in_let() {
assert_expr!("let a = { let b = 42 }");
}
#[test]
fn expect_let_in_let_return() {
assert_expr!(
"
let a = {
let b = 42
b
}
"
);
}
#[test]
fn expect_let_in_let_parens() {
assert_expr!("let a = ( let b = 42 )");
}
#[test]
fn expect_expect_let() {
assert_expr!("expect { let a = 42 } = foo");
}
}

View File

@ -17,6 +17,16 @@ pub fn parser(
just(Token::RightParen),
),
))
.map_with_span(|e, span| {
if matches!(e, UntypedExpr::Assignment { .. }) {
UntypedExpr::Sequence {
location: span,
expressions: vec![e],
}
} else {
e
}
})
}
#[cfg(test)]

View File

@ -0,0 +1,40 @@
---
source: crates/aiken-lang/src/parser/expr/assignment.rs
description: "Code:\n\nexpect { let a = 42 } = foo"
---
Assignment {
location: 0..21,
value: Sequence {
location: 7..21,
expressions: [
Assignment {
location: 9..19,
value: UInt {
location: 17..19,
value: "42",
base: Decimal {
numeric_underscore: false,
},
},
pattern: Var {
location: 13..14,
name: "a",
},
kind: Let,
annotation: None,
},
],
},
pattern: Constructor {
is_record: false,
location: 0..21,
name: "True",
arguments: [],
module: None,
constructor: (),
with_spread: false,
tipo: (),
},
kind: Expect,
annotation: None,
}

View File

@ -0,0 +1,34 @@
---
source: crates/aiken-lang/src/parser/expr/assignment.rs
description: "Code:\n\nlet a = { let b = 42 }"
---
Assignment {
location: 0..22,
value: Sequence {
location: 8..22,
expressions: [
Assignment {
location: 10..20,
value: UInt {
location: 18..20,
value: "42",
base: Decimal {
numeric_underscore: false,
},
},
pattern: Var {
location: 14..15,
name: "b",
},
kind: Let,
annotation: None,
},
],
},
pattern: Var {
location: 4..5,
name: "a",
},
kind: Let,
annotation: None,
}

View File

@ -0,0 +1,34 @@
---
source: crates/aiken-lang/src/parser/expr/assignment.rs
description: "Code:\n\nlet a = ( let b = 42 )"
---
Assignment {
location: 0..22,
value: Sequence {
location: 8..22,
expressions: [
Assignment {
location: 10..20,
value: UInt {
location: 18..20,
value: "42",
base: Decimal {
numeric_underscore: false,
},
},
pattern: Var {
location: 14..15,
name: "b",
},
kind: Let,
annotation: None,
},
],
},
pattern: Var {
location: 4..5,
name: "a",
},
kind: Let,
annotation: None,
}

View File

@ -0,0 +1,38 @@
---
source: crates/aiken-lang/src/parser/expr/assignment.rs
description: "Code:\n\nlet a = {\n let b = 42\n b\n}\n"
---
Assignment {
location: 0..28,
value: Sequence {
location: 12..26,
expressions: [
Assignment {
location: 12..22,
value: UInt {
location: 20..22,
value: "42",
base: Decimal {
numeric_underscore: false,
},
},
pattern: Var {
location: 16..17,
name: "b",
},
kind: Let,
annotation: None,
},
Var {
location: 25..26,
name: "b",
},
],
},
pattern: Var {
location: 4..5,
name: "a",
},
kind: Let,
annotation: None,
}

View File

@ -0,0 +1,15 @@
---
source: crates/aiken-lang/src/parser/expr/assignment.rs
description: "Invalid code (parse error):\n\nlet a =\n// foo\nlet b = 42\n"
---
[
ParseError {
kind: UnfinishedAssignmentRightHandSide,
span: 0..25,
while_parsing: None,
expected: {},
label: Some(
"invalid assignment right-hand side",
),
},
]

View File

@ -28,15 +28,28 @@ macro_rules! assert_expr {
let stream = chumsky::Stream::from_iter($crate::ast::Span::create(tokens.len(), 1), tokens.into_iter());
let result = $crate::parser::expr::sequence().parse(stream).unwrap();
let result = $crate::parser::expr::sequence().parse(stream);
insta::with_settings!({
description => concat!("Code:\n\n", indoc::indoc! { $code }),
prepend_module_to_snapshot => false,
omit_expression => true
}, {
insta::assert_debug_snapshot!(result);
});
match result {
Ok(expr) => {
insta::with_settings!({
description => concat!("Code:\n\n", indoc::indoc! { $code }),
prepend_module_to_snapshot => false,
omit_expression => true
}, {
insta::assert_debug_snapshot!(expr);
})
},
Err(err) => {
insta::with_settings!({
description => concat!("Invalid code (parse error):\n\n", indoc::indoc! { $code }),
prepend_module_to_snapshot => false,
omit_expression => true
}, {
insta::assert_debug_snapshot!(err);
})
}
}
};
}

View File

@ -407,10 +407,10 @@ Perhaps, try the following:
as, expect, check, const, else, fn, if, is, let, opaque, pub, test, todo, trace, type, use, when"#))]
KeywordInModuleName { name: String, keyword: String },
#[error("I discovered a function which is ending with an assignment.\n")]
#[error("I discovered a block which is ending with an assignment.\n")]
#[diagnostic(url("https://aiken-lang.org/language-tour/functions#named-functions"))]
#[diagnostic(code("illegal::return"))]
#[diagnostic(help(r#"In Aiken, functions must return an explicit result in the form of an expression. While assignments are technically speaking expressions, they aren't allowed to be the last expression of a function because they convey a different meaning and this could be error-prone.
#[diagnostic(help(r#"In Aiken, code blocks (such as function bodies) must return an explicit result in the form of an expression. While assignments are technically speaking expressions, they aren't allowed to be the last expression of a function because they convey a different meaning and this could be error-prone.
If you really meant to return that last expression, try to replace it with the following:

View File

@ -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(

View File

@ -1,39 +0,0 @@
{
"preamble": {
"title": "aiken-lang/acceptance_test_090",
"version": "0.0.0",
"plutusVersion": "v2",
"compiler": {
"name": "Aiken",
"version": "v1.0.21-alpha+9f263c4"
}
},
"validators": [
{
"title": "foo.spend",
"datum": {
"title": "datum",
"schema": {
"$ref": "#/definitions/Int"
}
},
"redeemer": {
"title": "_redeemer",
"schema": {
"$ref": "#/definitions/Data"
}
},
"compiledCode": "583f010000322223253330053370e00290487777c9cfdde5c8f27bf4c1637fc55b5eeef7d8c4d9e0d4454967ff7d6e7ee6e242eb60c6318a4c26cac6eb400d5cd1",
"hash": "d18aa035514acb988a34d33fc246420c5b0eca4f3f947ce95e294447"
}
],
"definitions": {
"Data": {
"title": "Data",
"description": "Any Plutus data."
},
"Int": {
"dataType": "integer"
}
}
}

View File

@ -0,0 +1,7 @@
# This file was generated by Aiken
# You typically do not need to edit this file
requirements = []
packages = []
[etags]

View File

@ -0,0 +1,2 @@
name = "aiken-lang/acceptance_test_092"
version = "0.0.0"

View File

@ -0,0 +1,41 @@
test foo_1() {
let a = {
let b = 42
b
}
a == 42
}
test foo_2() {
expect Some(a) = {
let b = 42
Some(b)
}
a == 42
}
test foo_3() {
let c = Some(42)
let a = {
expect Some(b) = c
b
}
a == 42
}
test foo_4() {
let a = {
let b = 2
let c = {
let d = 14
d * b
}
c + 14
}
a == 42
}