Skip to content

Commit

Permalink
add help text to parser errors
Browse files Browse the repository at this point in the history
  • Loading branch information
sezna committed Aug 9, 2024
1 parent ad2a07b commit 27b3166
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 64 deletions.
29 changes: 14 additions & 15 deletions compiler/qsc_parse/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,12 @@ fn expr_base(s: &mut ParserContext) -> Result<Box<Expr>> {
let vars = match pat(s) {
Ok(o) => o,
Err(e) => {
if let (TokenKind::Open(Delim::Paren), ErrorKind::Token(expected, received, span)) = (peeked.kind,& e.0) {
return Err(Error(ErrorKind::RuleWithHelp(
*expected, *received, *span, "parenthesis are not permitted around for loop iterations"
)));
} else {
return Err(e);
if let (TokenKind::Open(Delim::Paren), ErrorKind::Token(..)) = (peeked.kind, &e.0) {
return Err(
e.with_help("parenthesis are not permitted around for-loop iterations")
);
}
return Err(e);
}
};
token(s, TokenKind::Keyword(Keyword::In))?;
Expand Down Expand Up @@ -221,7 +220,7 @@ fn expr_base(s: &mut ParserContext) -> Result<Box<Expr>> {
} else if let Some(p) = opt(s, single_ident_path)? {
Ok(Box::new(ExprKind::Path(p)))
} else {
Err(Error(ErrorKind::Rule(
Err(Error::new(ErrorKind::Rule(
"expression",
s.peek().kind,
s.peek().span,
Expand Down Expand Up @@ -306,7 +305,7 @@ fn expr_set(s: &mut ParserContext) -> Result<Box<ExprKind>> {
let rhs = expr(s)?;
Ok(Box::new(ExprKind::AssignOp(closed_bin_op(op), lhs, rhs)))
} else {
Err(Error(ErrorKind::Rule(
Err(Error::new(ErrorKind::Rule(
"assignment operator",
s.peek().kind,
s.peek().span,
Expand Down Expand Up @@ -367,7 +366,7 @@ fn expr_interpolate(s: &mut ParserContext) -> Result<Vec<StringComponent>> {
let TokenKind::String(StringToken::Interpolated(InterpolatedStart::DollarQuote, mut end)) =
token.kind
else {
return Err(Error(ErrorKind::Rule(
return Err(Error::new(ErrorKind::Rule(
"interpolated string",
token.kind,
token.span,
Expand All @@ -388,7 +387,7 @@ fn expr_interpolate(s: &mut ParserContext) -> Result<Vec<StringComponent>> {
let TokenKind::String(StringToken::Interpolated(InterpolatedStart::RBrace, next_end)) =
token.kind
else {
return Err(Error(ErrorKind::Rule(
return Err(Error::new(ErrorKind::Rule(
"interpolated string",
token.kind,
token.span,
Expand Down Expand Up @@ -431,20 +430,20 @@ fn lit_token(lexeme: &str, token: Token) -> Result<Option<Lit>> {
let offset = if radix == Radix::Decimal { 0 } else { 2 };
let lexeme = &lexeme[offset..lexeme.len() - 1]; // Slice off prefix and suffix.
let value = BigInt::from_str_radix(lexeme, radix.into())
.map_err(|_| Error(ErrorKind::Lit("big-integer", token.span)))?;
.map_err(|_| Error::new(ErrorKind::Lit("big-integer", token.span)))?;
Ok(Some(Lit::BigInt(Box::new(value))))
}
TokenKind::Float => {
let lexeme = lexeme.replace('_', "");
let value = lexeme
.parse()
.map_err(|_| Error(ErrorKind::Lit("floating-point", token.span)))?;
.map_err(|_| Error::new(ErrorKind::Lit("floating-point", token.span)))?;
Ok(Some(Lit::Double(value)))
}
TokenKind::Int(radix) => {
let offset = if radix == Radix::Decimal { 0 } else { 2 };
let value = lit_int(&lexeme[offset..], radix.into())
.ok_or(Error(ErrorKind::Lit("integer", token.span)))?;
.ok_or(Error::new(ErrorKind::Lit("integer", token.span)))?;
Ok(Some(Lit::Int(value)))
}
TokenKind::String(StringToken::Normal) => {
Expand All @@ -457,7 +456,7 @@ fn lit_token(lexeme: &str, token: Token) -> Result<Option<Lit>> {
let index: u32 = index.try_into().expect("index should fit into u32");
let lo = token.span.lo + index + 2;
let span = Span { lo, hi: lo + 1 };
Error(ErrorKind::Escape(ch, span))
Error::new(ErrorKind::Escape(ch, span))
})?;
Ok(Some(Lit::String(string.into())))
}
Expand Down Expand Up @@ -771,7 +770,7 @@ fn expr_as_pat(expr: Expr) -> Result<Box<Pat>> {
.collect::<Result<_>>()?;
Ok(PatKind::Tuple(pats))
}
_ => Err(Error(ErrorKind::Convert(
_ => Err(Error::new(ErrorKind::Convert(
"pattern",
"expression",
expr.span,
Expand Down
20 changes: 19 additions & 1 deletion compiler/qsc_parse/src/expr/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2527,6 +2527,24 @@ fn friendly_error_on_parenthesized_for() {
check(
expr,
"for (x in xs) { () }",
&expect![[r#""#]]
&expect![[r#"
Error(
Token(
Close(
Paren,
),
Keyword(
In,
),
Span {
lo: 7,
hi: 9,
},
),
Some(
"parenthesis are not permitted around for-loop iterations",
),
)
"#]],
);
}
52 changes: 34 additions & 18 deletions compiler/qsc_parse/src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,19 @@ pub(super) fn parse(s: &mut ParserContext) -> Result<Box<Item>> {
Box::new(ItemKind::ImportOrExport(decl))
} else if visibility.is_some() {
let err_item = default(s.span(lo));
s.push_error(Error(ErrorKind::FloatingVisibility(err_item.span)));
s.push_error(Error::new(ErrorKind::FloatingVisibility(err_item.span)));
return Ok(err_item);
} else if !attrs.is_empty() {
let err_item = default(s.span(lo));
s.push_error(Error(ErrorKind::FloatingAttr(err_item.span)));
s.push_error(Error::new(ErrorKind::FloatingAttr(err_item.span)));
return Ok(err_item);
} else if doc.is_some() {
let err_item = default(s.span(lo));
s.push_error(Error(ErrorKind::FloatingDocComment(err_item.span)));
s.push_error(Error::new(ErrorKind::FloatingDocComment(err_item.span)));
return Ok(err_item);
} else {
let p = s.peek();
return Err(Error(ErrorKind::Rule("item", p.kind, p.span)));
return Err(Error::new(ErrorKind::Rule("item", p.kind, p.span)));
};

Ok(Box::new(Item {
Expand Down Expand Up @@ -133,7 +133,7 @@ fn parse_top_level_node(s: &mut ParserContext) -> Result<TopLevelNode> {
if let StmtKind::Item(item) = &mut *stmt.kind {
item.doc = doc.into();
} else if !doc.is_empty() {
return Err(Error(ErrorKind::Rule("item", kind, span)));
return Err(Error::new(ErrorKind::Rule("item", kind, span)));
}
Ok(TopLevelNode::Stmt(stmt))
}
Expand All @@ -153,7 +153,10 @@ pub fn parse_implicit_namespace(source_name: &str, s: &mut ParserContext) -> Res
let items = parse_namespace_block_contents(s)?;
recovering_token(s, TokenKind::Eof);
if items.is_empty() || s.peek().kind != TokenKind::Eof {
return Err(Error(ErrorKind::ExpectedItem(s.peek().kind, s.span(lo))));
return Err(Error::new(ErrorKind::ExpectedItem(
s.peek().kind,
s.span(lo),
)));
}
let span = s.span(lo);
let namespace_name = source_name_to_namespace_name(source_name, span)?;
Expand All @@ -177,7 +180,7 @@ fn source_name_to_namespace_name(raw: &str, span: Span) -> Result<Idents> {
match component {
std::path::Component::Normal(name) => {
// strip the extension off, if there is one
let mut name = name.to_str().ok_or(Error(ErrorKind::InvalidFileName(
let mut name = name.to_str().ok_or(Error::new(ErrorKind::InvalidFileName(
span,
name.to_string_lossy().to_string(),
)))?;
Expand All @@ -192,7 +195,12 @@ fn source_name_to_namespace_name(raw: &str, span: Span) -> Result<Idents> {

namespace.push(ident);
}
_ => return Err(Error(ErrorKind::InvalidFileName(span, raw.to_string()))),
_ => {
return Err(Error::new(ErrorKind::InvalidFileName(
span,
raw.to_string(),
)))
}
}
}

Expand All @@ -216,9 +224,9 @@ fn validate_namespace_name(error_span: Span, name: &str) -> Result<Ident> {
// we just directly use the ident parser here instead of trying to recreate
// validation rules
let ident = ident(&mut s)
.map_err(|_| Error(ErrorKind::InvalidFileName(error_span, name.to_string())))?;
.map_err(|_| Error::new(ErrorKind::InvalidFileName(error_span, name.to_string())))?;
if s.peek().kind != TokenKind::Eof {
return Err(Error(ErrorKind::InvalidFileName(
return Err(Error::new(ErrorKind::InvalidFileName(
error_span,
name.to_string(),
)));
Expand Down Expand Up @@ -311,7 +319,7 @@ fn parse_open(s: &mut ParserContext) -> Result<Box<ItemKind>> {
// Peek to see if the next token is a dot -- this means it is likely a dot ident alias, and
// we want to provide a more helpful error message
if s.peek().kind == TokenKind::Dot {
return Err(Error(ErrorKind::DotIdentAlias(s.peek().span)));
return Err(Error::new(ErrorKind::DotIdentAlias(s.peek().span)));
}

token(s, TokenKind::Semi)?;
Expand Down Expand Up @@ -410,7 +418,11 @@ fn parse_ty_def(s: &mut ParserContext) -> Result<Box<TyDef>> {

fn ty_as_ident(ty: Ty) -> Result<Box<Ident>> {
let TyKind::Path(path) = *ty.kind else {
return Err(Error(ErrorKind::Convert("identifier", "type", ty.span)));
return Err(Error::new(ErrorKind::Convert(
"identifier",
"type",
ty.span,
)));
};
if let Path {
segments: None,
Expand All @@ -420,7 +432,11 @@ fn ty_as_ident(ty: Ty) -> Result<Box<Ident>> {
{
Ok(name)
} else {
Err(Error(ErrorKind::Convert("identifier", "type", ty.span)))
Err(Error::new(ErrorKind::Convert(
"identifier",
"type",
ty.span,
)))
}
}

Expand All @@ -433,7 +449,7 @@ fn parse_callable_decl(s: &mut ParserContext) -> Result<Box<CallableDecl>> {
CallableKind::Operation
} else {
let token = s.peek();
return Err(Error(ErrorKind::Rule(
return Err(Error::new(ErrorKind::Rule(
"callable declaration",
token.kind,
token.span,
Expand Down Expand Up @@ -510,7 +526,7 @@ fn parse_spec_decl(s: &mut ParserContext) -> Result<Box<SpecDecl>> {
Spec::Ctl
}
} else {
return Err(Error(ErrorKind::Rule(
return Err(Error::new(ErrorKind::Rule(
"specialization",
s.peek().kind,
s.peek().span,
Expand Down Expand Up @@ -544,7 +560,7 @@ fn parse_spec_gen(s: &mut ParserContext) -> Result<SpecGen> {
} else if token(s, TokenKind::Keyword(Keyword::Slf)).is_ok() {
Ok(SpecGen::Slf)
} else {
Err(Error(ErrorKind::Rule(
Err(Error::new(ErrorKind::Rule(
"specialization generator",
s.peek().kind,
s.peek().span,
Expand All @@ -557,7 +573,7 @@ pub(super) fn check_input_parens(inputs: &Pat) -> Result<()> {
if matches!(*inputs.kind, PatKind::Paren(_) | PatKind::Tuple(_)) {
Ok(())
} else {
Err(Error(ErrorKind::MissingParens(inputs.span)))
Err(Error::new(ErrorKind::MissingParens(inputs.span)))
}
}

Expand All @@ -578,7 +594,7 @@ fn parse_import_or_export(s: &mut ParserContext) -> Result<ImportOrExportDecl> {
TokenKind::Keyword(Keyword::Export) => true,
TokenKind::Keyword(Keyword::Import) => false,
_ => {
return Err(Error(ErrorKind::Rule(
return Err(Error::new(ErrorKind::Rule(
"import or export",
s.peek().kind,
s.peek().span,
Expand Down
73 changes: 61 additions & 12 deletions compiler/qsc_parse/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,77 @@ use std::rc::Rc;
use std::result;
use thiserror::Error;

#[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)]
#[error(transparent)]
#[diagnostic(transparent)]
pub struct Error(ErrorKind, #[help] Option<String>);
#[derive(Clone, Eq, Error, PartialEq)]
pub struct Error(ErrorKind, Option<String>);

impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
ErrorKind::fmt(&self.0, f)
}
}

impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut formatter = f.debug_tuple("Error");
if self.1.is_some() {
formatter.field(&self.0).field(&self.1)
} else {
formatter.field(&self.0)
}
.finish()
}
}

impl Diagnostic for Error {
fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
self.0.code()
}

fn severity(&self) -> Option<miette::Severity> {
self.0.severity()
}

fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
self.1
.clone()
.map(|help| Box::new(help) as Box<dyn std::fmt::Display>)
}

fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
self.0.url()
}

fn source_code(&self) -> Option<&dyn miette::SourceCode> {
self.0.source_code()
}

fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
self.0.labels()
}

fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
self.0.related()
}

fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
self.0.diagnostic_source()
}
}

impl Error {
#[must_use]
pub fn with_offset(self, offset: u32) -> Self {
Self(self.0.with_offset(offset), self.1)
}

pub fn new(kind: ErrorKind) -> Self {
#[must_use]
pub(crate) fn new(kind: ErrorKind) -> Self {
Self(kind, None)
}

pub fn with_help(self, help_text: String) -> Self {
Self(self.0, Some(help_text))
#[must_use]
pub fn with_help(self, help_text: impl Into<String>) -> Self {
Self(self.0, Some(help_text.into()))
}
}

Expand Down Expand Up @@ -74,10 +128,6 @@ enum ErrorKind {
#[diagnostic(code("Qsc.Parse.Rule"))]
Rule(&'static str, TokenKind, #[label] Span),
#[error("expected {0}, found {1}")]
#[diagnostic(code("Qsc.Parse.Rule"))]
#[help("{3}")]
RuleWithHelp(&'static str, TokenKind, #[label] Span, &'static str),
#[error("expected {0}, found {1}")]
#[diagnostic(code("Qsc.Parse.Convert"))]
Convert(&'static str, &'static str, #[label] Span),
#[error("expected statement to end with a semicolon")]
Expand Down Expand Up @@ -108,7 +158,6 @@ impl ErrorKind {
Self::Escape(ch, span) => Self::Escape(ch, span + offset),
Self::Token(expected, actual, span) => Self::Token(expected, actual, span + offset),
Self::Rule(name, token, span) => Self::Rule(name, token, span + offset),
Self::RuleWithHelp(name, token, span, help) => Self::RuleWithHelp(name, token, span + offset, help),
Self::Convert(expected, actual, span) => Self::Convert(expected, actual, span + offset),
Self::MissingSemi(span) => Self::MissingSemi(span + offset),
Self::MissingParens(span) => Self::MissingParens(span + offset),
Expand Down
Loading

0 comments on commit 27b3166

Please sign in to comment.