feat: parser and check fixes

- do not erase sequences if the sole expression is an assignment
- emit parse error if an assignment is assigned to an assignment
- do not allow assignments in logical op chains
This commit is contained in:
rvcas 2024-01-19 14:32:21 -05:00
parent 8a90e9eda0
commit 25a837ab3f
No known key found for this signature in database
GPG Key ID: C09B64E263F7D68C
7 changed files with 105 additions and 6 deletions

View File

@ -10,6 +10,10 @@
### Fixed ### Fixed
- **aiken-lang**: Fix flat encoding and decoding of large integer values. @KtorZ - **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 - **aiken**: Ensures that test expected to fail that return `False` are considered to pass & improve error reporting when they fail. @KtorZ
### Removed ### 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 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<String>) -> Self { pub fn invalid_tuple_index(span: Span, index: String, suffix: Option<String>) -> Self {
let hint = suffix.map(|suffix| format!("Did you mean '{index}{suffix}'?")); let hint = suffix.map(|suffix| format!("Did you mean '{index}{suffix}'?"));
Self { Self {
@ -163,6 +173,16 @@ pub enum ErrorKind {
hint: Option<String>, hint: Option<String>,
}, },
#[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))] #[error("I tripped over a {}", fmt_curve_type(.curve))]
PointNotOnCurve { curve: CurveType }, PointNotOnCurve { curve: CurveType },

View File

@ -14,15 +14,19 @@ pub fn let_(
.then(just(Token::Colon).ignore_then(annotation()).or_not()) .then(just(Token::Colon).ignore_then(annotation()).or_not())
.then_ignore(just(Token::Equal)) .then_ignore(just(Token::Equal))
.then(r.clone()) .then(r.clone())
.map_with_span( .validate(move |((pattern, annotation), value), span, emit| {
move |((pattern, annotation), value), span| UntypedExpr::Assignment { if matches!(value, UntypedExpr::Assignment { .. }) {
emit(ParseError::invalid_assignment_right_hand_side(span))
}
UntypedExpr::Assignment {
location: span, location: span,
value: Box::new(value), value: Box::new(value),
pattern, pattern,
kind: ast::AssignmentKind::Let, kind: ast::AssignmentKind::Let,
annotation, annotation,
}, }
) })
} }
pub fn expect( pub fn expect(
@ -36,7 +40,7 @@ pub fn expect(
.or_not(), .or_not(),
) )
.then(r.clone()) .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(|| { let (pattern, annotation) = opt_pattern.unwrap_or_else(|| {
( (
ast::UntypedPattern::Constructor { 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 { UntypedExpr::Assignment {
location: span, location: span,
value: Box::new(value), value: Box::new(value),

View File

@ -11,7 +11,17 @@ pub fn parser(
choice(( choice((
sequence sequence
.clone() .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( sequence.clone().delimited_by(
choice((just(Token::LeftParen), just(Token::NewLineLeftParen))), choice((just(Token::LeftParen), just(Token::NewLineLeftParen))),
just(Token::RightParen), just(Token::RightParen),

View File

@ -1650,6 +1650,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
let mut typed_expressions = vec![]; let mut typed_expressions = vec![];
for expression in expressions { for expression in expressions {
assert_no_assignment(&expression)?;
let typed_expression = self.infer(expression)?; let typed_expression = self.infer(expression)?;
self.unify( self.unify(