diff --git a/crates/typst/src/syntax/node.rs b/crates/typst/src/syntax/node.rs index 6a66416de..3bbecb6cb 100644 --- a/crates/typst/src/syntax/node.rs +++ b/crates/typst/src/syntax/node.rs @@ -135,6 +135,13 @@ impl SyntaxNode { } } + /// Adds a user-presentable hint if this is an error node. + pub fn hint(&mut self, hint: impl Into) { + if let Repr::Error(error) = &mut self.0 { + Arc::make_mut(error).hint(hint); + } + } + /// The error messages for this node and its descendants. pub fn errors(&self) -> Vec { if !self.erroneous() { @@ -142,7 +149,8 @@ impl SyntaxNode { } if let Repr::Error(error) = &self.0 { - vec![SourceError::new(error.span, error.message.clone())] + vec![SourceError::new(error.span, error.message.clone()) + .with_hints(error.hints.to_owned())] } else { self.children() .filter(|node| node.erroneous()) @@ -539,6 +547,9 @@ struct ErrorNode { text: EcoString, /// The node's span. span: Span, + /// Additonal hints to the user, indicating how this error could be avoided + /// or worked around. + hints: Vec, } impl ErrorNode { @@ -548,6 +559,7 @@ impl ErrorNode { message: message.into(), text: text.into(), span: Span::detached(), + hints: vec![], } } @@ -555,6 +567,11 @@ impl ErrorNode { fn len(&self) -> usize { self.text.len() } + + /// Add a user-presentable hint to this error node. + fn hint(&mut self, hint: impl Into) { + self.hints.push(hint.into()); + } } impl Debug for ErrorNode { diff --git a/crates/typst/src/syntax/parser.rs b/crates/typst/src/syntax/parser.rs index 54670df5f..4849ff791 100644 --- a/crates/typst/src/syntax/parser.rs +++ b/crates/typst/src/syntax/parser.rs @@ -1066,7 +1066,8 @@ fn for_loop(p: &mut Parser) { p.assert(SyntaxKind::For); pattern(p); if p.at(SyntaxKind::Comma) { - p.expected("keyword `in` - did you mean to use a destructuring pattern?"); + p.expected("keyword `in`"); + p.hint("did you mean to use a destructuring pattern?"); if !p.eat_if(SyntaxKind::Ident) { p.eat_if(SyntaxKind::Underscore); } @@ -1613,6 +1614,15 @@ impl<'s> Parser<'s> { self.skip(); } + // Adds a hint to the last node, if the last node is an error. + fn hint(&mut self, hint: impl Into) { + self.unskip(); + if let Some(last) = self.nodes.last_mut() { + last.hint(hint); + } + self.skip(); + } + fn expected_at(&mut self, m: Marker, thing: &str) { let message = eco_format!("expected {}", thing); let error = SyntaxNode::error(message, ""); diff --git a/tests/typ/compiler/for.typ b/tests/typ/compiler/for.typ index 7a0d96015..b2a2287d3 100644 --- a/tests/typ/compiler/for.typ +++ b/tests/typ/compiler/for.typ @@ -92,7 +92,8 @@ --- // Destructuring without parentheses. -// Error: 7 expected keyword `in` - did you mean to use a destructuring pattern? +// Error: 7 expected keyword `in` +// Hint: 7 did you mean to use a destructuring pattern? #for k, v in (a: 4, b: 5) { dont-care }