Better trace parsing + default behaviour.

Somehow, we allow traces with no continuation after and currently
  default to a Todo. That todo is completely invisible from a user
  standpoint and return a generic `a`.

  So it is pretty easy for someone to think their program is okay,
  compiles, and have no issues, while simply crashing at runtime because
  of an invisible todo.

  Hence, I've changed that to default to `Void` instead, which is more
  sensible as a default for an empty trace.

  Also, I've made the parser fail with one puts a colon for label, but
  doesn't add any value to display.

  Fixes #1113.

Signed-off-by: KtorZ <matthias.benkort@gmail.com>
This commit is contained in:
KtorZ 2025-03-01 16:59:22 +01:00
parent 222d244bcf
commit 9b8137c056
No known key found for this signature in database
GPG Key ID: 33173CB6F77F4277
6 changed files with 112 additions and 33 deletions

View File

@ -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":
}
"#
);
}
}

View File

@ -0,0 +1,72 @@
---
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"
---
[
ParseError {
kind: Unexpected(
Token(
RightBrace,
),
),
span: 34..35,
while_parsing: None,
expected: {
Token(
If,
),
Token(
Expect,
),
Token(
Minus,
),
Token(
When,
),
Token(
And,
),
Token(
Or,
),
Token(
LeftParen,
),
Token(
Todo,
),
Token(
LeftSquare,
),
Token(
NewLineMinus,
),
Token(
Bang,
),
Token(
Fail,
),
Token(
NewLineLeftParen,
),
Token(
LeftBrace,
),
Token(
Trace,
),
Token(
Let,
),
Token(
Hash,
),
Token(
Fn,
),
},
label: None,
},
]

View File

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

View File

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

View File

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

View File

@ -3332,6 +3332,22 @@ fn dangling_let_in_block() {
)
}
#[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#"