commit
97c382715f
|
@ -1,5 +1,13 @@
|
|||
# Changelog
|
||||
|
||||
## v1.1.14 - UNRELEASED
|
||||
|
||||
### Changed
|
||||
|
||||
- **aiken-lang**: Prevent (type error) backpassing blocks with empty continuation. See [#1111](https://github.com/aiken-lang/aiken/issues/1111). @KtorZ
|
||||
- **aiken-lang**: Change default placeholder for `trace` to `Void` instead of `todo`. @KtorZ
|
||||
- **aiken-lang**: Disallow (parse error) dangling colon `:` in traces. See [#1113](https://github.com/aiken-lang/aiken/issues/1113). @KtorZ
|
||||
|
||||
## v1.1.13 - 2025-02-26
|
||||
|
||||
### Added
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::{
|
|||
parser::token::Token,
|
||||
};
|
||||
use indoc::formatdoc;
|
||||
use itertools::Itertools;
|
||||
use miette::Diagnostic;
|
||||
use owo_colors::{OwoColorize, Stream::Stdout};
|
||||
use std::collections::HashSet;
|
||||
|
@ -18,6 +19,7 @@ use std::collections::HashSet;
|
|||
"I am looking for one of the following patterns:\n{}",
|
||||
expected
|
||||
.iter()
|
||||
.sorted()
|
||||
.map(|x| format!(
|
||||
"→ {}",
|
||||
x.to_aiken()
|
||||
|
@ -320,7 +322,7 @@ fn fmt_unknown_curve(curve: &String, point: &Option<String>) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Diagnostic, thiserror::Error)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Diagnostic, thiserror::Error)]
|
||||
pub enum Pattern {
|
||||
#[error("I found an unexpected char '{0:?}'.")]
|
||||
#[diagnostic(help("Try removing it!"))]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
ast::TraceKind,
|
||||
ast::{well_known, TraceKind},
|
||||
expr::UntypedExpr,
|
||||
parser::{
|
||||
error::{ParseError, Pattern},
|
||||
|
@ -32,7 +32,8 @@ pub fn parser<'a>(
|
|||
choice((just(Token::Colon), just(Token::Comma)))
|
||||
.then(
|
||||
choice((string::hybrid(), expression.clone()))
|
||||
.separated_by(just(Token::Comma)),
|
||||
.separated_by(just(Token::Comma))
|
||||
.at_least(1),
|
||||
)
|
||||
.validate(|(token, arguments), span, emit| {
|
||||
if token != Token::Colon {
|
||||
|
@ -53,7 +54,10 @@ pub fn parser<'a>(
|
|||
|((label, arguments), continuation), span| UntypedExpr::Trace {
|
||||
kind: TraceKind::Trace,
|
||||
location: span,
|
||||
then: Box::new(continuation.unwrap_or_else(|| UntypedExpr::todo(None, span))),
|
||||
then: Box::new(continuation.unwrap_or_else(|| UntypedExpr::Var {
|
||||
location: span,
|
||||
name: well_known::VOID.to_string(),
|
||||
})),
|
||||
label: Box::new(label),
|
||||
arguments,
|
||||
},
|
||||
|
@ -193,4 +197,15 @@ mod tests {
|
|||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trace_dangling_colons() {
|
||||
assert_expr!(
|
||||
r#"
|
||||
let debug = fn() {
|
||||
trace "foo":
|
||||
}
|
||||
"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,14 +2,4 @@
|
|||
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",
|
||||
),
|
||||
},
|
||||
]
|
||||
I spotted an unfinished assignment.
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
source: crates/aiken-lang/src/parser/expr/fail_todo_trace.rs
|
||||
description: "Invalid code (parse error):\n\nlet debug = fn() {\n trace \"foo\":\n}\n"
|
||||
---
|
||||
I found an unexpected token '}'.
|
|
@ -5,17 +5,9 @@ description: "Code:\n\ntrace some_var\n"
|
|||
Trace {
|
||||
kind: Trace,
|
||||
location: 0..14,
|
||||
then: Trace {
|
||||
kind: Todo,
|
||||
then: Var {
|
||||
location: 0..14,
|
||||
then: ErrorTerm {
|
||||
location: 0..14,
|
||||
},
|
||||
label: String {
|
||||
location: 0..14,
|
||||
value: "aiken::todo",
|
||||
},
|
||||
arguments: [],
|
||||
name: "Void",
|
||||
},
|
||||
label: Var {
|
||||
location: 6..14,
|
||||
|
|
|
@ -5,17 +5,9 @@ description: "Code:\n\ntrace foo: \"bar\"\n"
|
|||
Trace {
|
||||
kind: Trace,
|
||||
location: 0..16,
|
||||
then: Trace {
|
||||
kind: Todo,
|
||||
then: Var {
|
||||
location: 0..16,
|
||||
then: ErrorTerm {
|
||||
location: 0..16,
|
||||
},
|
||||
label: String {
|
||||
location: 0..16,
|
||||
value: "aiken::todo",
|
||||
},
|
||||
arguments: [],
|
||||
name: "Void",
|
||||
},
|
||||
label: Var {
|
||||
location: 6..9,
|
||||
|
|
|
@ -5,17 +5,9 @@ description: "Code:\n\ntrace \"foo\": @\"bar\", baz\n"
|
|||
Trace {
|
||||
kind: Trace,
|
||||
location: 0..24,
|
||||
then: Trace {
|
||||
kind: Todo,
|
||||
then: Var {
|
||||
location: 0..24,
|
||||
then: ErrorTerm {
|
||||
location: 0..24,
|
||||
},
|
||||
label: String {
|
||||
location: 0..24,
|
||||
value: "aiken::todo",
|
||||
},
|
||||
arguments: [],
|
||||
name: "Void",
|
||||
},
|
||||
label: String {
|
||||
location: 6..11,
|
||||
|
|
|
@ -2,14 +2,4 @@
|
|||
source: crates/aiken-lang/src/parser/expr/when/mod.rs
|
||||
description: "Invalid code (parse error):\n\nwhen a is {\n 2 if x > 1 -> 3\n _ -> 1\n}\n"
|
||||
---
|
||||
[
|
||||
ParseError {
|
||||
kind: DeprecatedWhenClause,
|
||||
span: 14..29,
|
||||
while_parsing: None,
|
||||
expected: {},
|
||||
label: Some(
|
||||
"deprecated",
|
||||
),
|
||||
},
|
||||
]
|
||||
I found a now-deprecated clause guard in a when/is expression.
|
||||
|
|
|
@ -2,14 +2,4 @@
|
|||
source: crates/aiken-lang/src/parser/pattern/bytearray.rs
|
||||
description: "Invalid code (parse error):\n\nwhen foo is {\n #<Bls12_381, G1>\"950dfd33da2682260c76038dfb8bad6e84ae9d599a3c151815945ac1e6ef6b1027cd917f3907479d20d636ce437a41f5\" -> False\n _ -> True\n}\n"
|
||||
---
|
||||
[
|
||||
ParseError {
|
||||
kind: PatternMatchOnCurvePoint,
|
||||
span: 18..132,
|
||||
while_parsing: None,
|
||||
expected: {},
|
||||
label: Some(
|
||||
"cannot pattern-match on curve point",
|
||||
),
|
||||
},
|
||||
]
|
||||
I choked on a curve point in a bytearray pattern.
|
||||
|
|
|
@ -2,14 +2,4 @@
|
|||
source: crates/aiken-lang/src/parser/pattern/bytearray.rs
|
||||
description: "Invalid code (parse error):\n\nwhen foo is {\n #<Bls12_381, G2>\"b0629fa1158c2d23a10413fe91d381a84d25e31d041cd0377d25828498fd02011b35893938ced97535395e4815201e67108bcd4665e0db25d602d76fa791fab706c54abf5e1a9e44b4ac1e6badf3d2ac0328f5e30be341677c8bac5dda7682f1\" -> False\n _ -> True\n}\n"
|
||||
---
|
||||
[
|
||||
ParseError {
|
||||
kind: PatternMatchOnCurvePoint,
|
||||
span: 18..228,
|
||||
while_parsing: None,
|
||||
expected: {},
|
||||
label: Some(
|
||||
"cannot pattern-match on curve point",
|
||||
),
|
||||
},
|
||||
]
|
||||
I choked on a curve point in a bytearray pattern.
|
||||
|
|
|
@ -2,14 +2,4 @@
|
|||
source: crates/aiken-lang/src/parser/pattern/string.rs
|
||||
description: "Invalid code (parse error):\n\nwhen foo is {\n @\"foo\" -> True\n}\n"
|
||||
---
|
||||
[
|
||||
ParseError {
|
||||
kind: PatternMatchOnString,
|
||||
span: 16..22,
|
||||
while_parsing: None,
|
||||
expected: {},
|
||||
label: Some(
|
||||
"cannot pattern-match on string",
|
||||
),
|
||||
},
|
||||
]
|
||||
I refuse to cooperate and match a utf-8 string.
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Hash, Eq, Copy, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, PartialOrd, Ord, Hash, Eq, Copy, serde::Serialize, serde::Deserialize,
|
||||
)]
|
||||
pub enum Base {
|
||||
Decimal { numeric_underscore: bool },
|
||||
Hexadecimal,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Hash, Eq)]
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Hash, Eq)]
|
||||
pub enum Token {
|
||||
Error(char),
|
||||
Name { name: String },
|
||||
|
|
|
@ -45,7 +45,7 @@ macro_rules! assert_expr {
|
|||
prepend_module_to_snapshot => false,
|
||||
omit_expression => true
|
||||
}, {
|
||||
insta::assert_debug_snapshot!(err);
|
||||
insta::assert_snapshot!(err.into_iter().map(|e| e.to_string()).collect::<Vec<_>>().join("\n"));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ fn check_module(
|
|||
|
||||
for (package, module) in extra {
|
||||
let mut warnings = vec![];
|
||||
|
||||
let typed_module = module
|
||||
.infer(
|
||||
&id_gen,
|
||||
|
@ -3309,6 +3310,44 @@ fn softcasting_unused_let_binding() {
|
|||
assert!(warnings.is_empty(), "should not contain any warnings");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dangling_let_in_block() {
|
||||
let source_code = r#"
|
||||
fn for_each(xs: List<Int>, with: fn(Int) -> a) -> a {
|
||||
todo
|
||||
}
|
||||
|
||||
test foo() {
|
||||
{
|
||||
let _ <- for_each([1, 2, 3])
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let result = check_validator(parse(source_code));
|
||||
|
||||
assert!(
|
||||
matches!(result, Err((_, Error::LastExpressionIsAssignment { .. }))),
|
||||
"{result:?}"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_trace_return() {
|
||||
let source_code = r#"
|
||||
fn debug() {
|
||||
trace @"patate": Void
|
||||
}
|
||||
|
||||
test foo() {
|
||||
debug()
|
||||
True
|
||||
}
|
||||
"#;
|
||||
|
||||
assert!(matches!(check_validator(parse(source_code)), Ok(..)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dangling_trace_let_standalone() {
|
||||
let source_code = r#"
|
||||
|
|
|
@ -1970,11 +1970,12 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
PipeTyper::infer(self, expressions)
|
||||
}
|
||||
|
||||
#[allow(clippy::result_large_err)]
|
||||
fn backpass(
|
||||
&mut self,
|
||||
breakpoint: UntypedExpr,
|
||||
mut continuation: Vec<UntypedExpr>,
|
||||
) -> UntypedExpr {
|
||||
) -> Result<UntypedExpr, Error> {
|
||||
let UntypedExpr::Assignment {
|
||||
location,
|
||||
value,
|
||||
|
@ -1985,6 +1986,15 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
unreachable!("backpass misuse: breakpoint isn't an Assignment ?!");
|
||||
};
|
||||
|
||||
if continuation.is_empty() {
|
||||
return Err(Error::LastExpressionIsAssignment {
|
||||
location,
|
||||
expr: *value,
|
||||
patterns: patterns.clone(),
|
||||
kind,
|
||||
});
|
||||
}
|
||||
|
||||
let value_location = value.location();
|
||||
|
||||
let call_location = Span {
|
||||
|
@ -2101,11 +2111,11 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
value: UntypedExpr::lambda(names, continuation, lambda_span),
|
||||
});
|
||||
|
||||
UntypedExpr::Call {
|
||||
Ok(UntypedExpr::Call {
|
||||
location: call_location,
|
||||
fun,
|
||||
arguments: new_arguments,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// This typically occurs on function captures. We do not try to assert anything on the
|
||||
|
@ -2136,15 +2146,15 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
};
|
||||
|
||||
if arguments.is_empty() {
|
||||
call
|
||||
Ok(call)
|
||||
} else {
|
||||
UntypedExpr::Fn {
|
||||
Ok(UntypedExpr::Fn {
|
||||
location: call_location,
|
||||
fn_style,
|
||||
arguments,
|
||||
body: call.into(),
|
||||
return_annotation,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2152,7 +2162,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
// with our continuation. If the expression isn't callable? No problem, the
|
||||
// type-checker will catch that eventually in exactly the same way as if the code was
|
||||
// written like that to begin with.
|
||||
_ => UntypedExpr::Call {
|
||||
_ => Ok(UntypedExpr::Call {
|
||||
location: call_location,
|
||||
fun: value,
|
||||
arguments: vec![CallArg {
|
||||
|
@ -2160,7 +2170,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
label: None,
|
||||
value: UntypedExpr::lambda(names, continuation, lambda_span),
|
||||
}],
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2203,7 +2213,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
}
|
||||
|
||||
if let Some(breakpoint) = breakpoint {
|
||||
prefix.push(self.backpass(breakpoint, suffix));
|
||||
prefix.push(self.backpass(breakpoint, suffix)?);
|
||||
return self.infer_seq(location, prefix);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue