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