From 2922c0aa6f8c1bd5898f51468eb31f3126dbecc2 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Fri, 19 Jul 2024 12:16:13 +0200 Subject: [PATCH] Display expected patterns/tokens in parse error when applicable. We've never been using those 'expected' tokens captured during parsing, which is lame because they contain useful information! This is much better than merely showing our infamous "Try removing it!" --- crates/aiken-lang/src/parser/error.rs | 70 ++++++++++++++----- .../src/parser/expr/fail_todo_trace.rs | 2 +- crates/aiken-lang/src/parser/token.rs | 2 +- crates/aiken-project/src/error.rs | 2 +- 4 files changed, 57 insertions(+), 19 deletions(-) diff --git a/crates/aiken-lang/src/parser/error.rs b/crates/aiken-lang/src/parser/error.rs index 0212e042..2877c7ba 100644 --- a/crates/aiken-lang/src/parser/error.rs +++ b/crates/aiken-lang/src/parser/error.rs @@ -9,6 +9,30 @@ use std::collections::HashSet; #[derive(Debug, Clone, Diagnostic, thiserror::Error)] #[error("{kind}\n")] +#[diagnostic( + help( + "{}", + match kind { + ErrorKind::Unexpected(..) if !expected.is_empty() => { + format!( + "I am looking for one of the following patterns:\n{}", + expected + .iter() + .map(|x| format!( + "→ {}", + x.to_aiken() + .if_supports_color(Stdout, |s| s.purple()) + )) + .collect::>() + .join("\n") + ) + }, + _ => { + kind.help().map(|x| x.to_string()).unwrap_or_default() + } + } + ) +)] pub struct ParseError { pub kind: ErrorKind, #[label] @@ -28,6 +52,16 @@ impl ParseError { self } + pub fn expected_but_got(expected: Pattern, got: Pattern, span: Span) -> Self { + Self { + kind: ErrorKind::Unexpected(got), + expected: HashSet::from_iter([expected]), + span, + while_parsing: None, + label: None, + } + } + pub fn invalid_assignment_right_hand_side(span: Span) -> Self { Self { kind: ErrorKind::UnfinishedAssignmentRightHandSide, @@ -163,7 +197,7 @@ pub enum ErrorKind { UnexpectedEnd, #[error("{0}")] - #[diagnostic(help("{}", .0.help().unwrap_or_else(|| Box::new(""))))] + #[diagnostic(help("{}", .0.help().unwrap_or_else(|| Box::new("")))) ] Unexpected(Pattern), #[error("I discovered an invalid tuple index.")] @@ -221,7 +255,9 @@ pub enum ErrorKind { HybridNotationInByteArray, #[error("I failed to understand a when clause guard.")] - #[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#checking-equality-and-ordering-in-patterns"))] + #[diagnostic(url( + "https://aiken-lang.org/language-tour/control-flow#checking-equality-and-ordering-in-patterns" + ))] #[diagnostic(help("{}", formatdoc! { r#"Clause guards are not as capable as standard expressions. While you can combine multiple clauses using '{operator_or}' and '{operator_and}', you can't do any arithmetic in there. They are mainly meant to compare pattern variables to some known constants using simple binary operators. @@ -278,15 +314,6 @@ pub enum Pattern { #[error("I found an unexpected token '{0}'.")] #[diagnostic(help("Try removing it!"))] Token(Token), - #[error("I found an unexpected literal value.")] - #[diagnostic(help("Try removing it!"))] - Literal, - #[error("I found an unexpected type name.")] - #[diagnostic(help("Try removing it!"))] - TypeIdent, - #[error("I found an unexpected identifier.")] - #[diagnostic(help("Try removing it!"))] - TermIdent, #[error("I found an unexpected end of input.")] End, #[error("I found a malformed list spread pattern.")] @@ -295,11 +322,6 @@ pub enum Pattern { #[error("I found an out-of-bound byte literal.")] #[diagnostic(help("Bytes must be between 0-255."))] Byte, - #[error("I found an unexpected pattern.")] - #[diagnostic(help( - "If no label is provided then only variables\nmatching a field name are allowed." - ))] - RecordPunning, #[error("I found an unexpected label.")] #[diagnostic(help("You can only use labels surrounded by curly braces"))] Label, @@ -308,11 +330,27 @@ pub enum Pattern { Discard, } +impl Pattern { + fn to_aiken(&self) -> String { + use Pattern::*; + match self { + Token(tok) => tok.to_string(), + Char(c) => c.to_string(), + End => "".to_string(), + Match => "A pattern (a discard, a var, etc...)".to_string(), + Byte => "A byte between [0; 255]".to_string(), + Label => "A label".to_string(), + Discard => "_".to_string(), + } + } +} + impl From for Pattern { fn from(c: char) -> Self { Self::Char(c) } } + impl From for Pattern { fn from(tok: Token) -> Self { Self::Token(tok) diff --git a/crates/aiken-lang/src/parser/expr/fail_todo_trace.rs b/crates/aiken-lang/src/parser/expr/fail_todo_trace.rs index be7c7d34..2cde45c8 100644 --- a/crates/aiken-lang/src/parser/expr/fail_todo_trace.rs +++ b/crates/aiken-lang/src/parser/expr/fail_todo_trace.rs @@ -2,7 +2,7 @@ use crate::{ ast::TraceKind, expr::UntypedExpr, parser::{ - error::ParseError, + error::{ParseError, Pattern}, expr::{string, when::clause}, token::Token, }, diff --git a/crates/aiken-lang/src/parser/token.rs b/crates/aiken-lang/src/parser/token.rs index 8f1fdbfa..78c754ae 100644 --- a/crates/aiken-lang/src/parser/token.rs +++ b/crates/aiken-lang/src/parser/token.rs @@ -183,6 +183,6 @@ impl fmt::Display for Token { Token::Validator => "validator", Token::Via => "via", }; - write!(f, "\"{s}\"") + write!(f, "{s}") } } diff --git a/crates/aiken-project/src/error.rs b/crates/aiken-project/src/error.rs index b262ed11..b5398018 100644 --- a/crates/aiken-project/src/error.rs +++ b/crates/aiken-project/src/error.rs @@ -324,7 +324,7 @@ impl Diagnostic for Error { "Try moving the shared code to a separate module that the others can depend on\n- {}", modules.join("\n- ") ))), - Error::Parse { error, .. } => error.kind.help(), + Error::Parse { error, .. } => error.help(), Error::Type { error, .. } => error.help(), Error::StandardIo(_) => None, Error::MissingManifest { .. } => Some(Box::new(