Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide quick fix for invalid syntax error #1133

Merged
134 changes: 128 additions & 6 deletions kclvm/error/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,9 +337,16 @@ pub enum ParseError {
Message {
message: String,
span: Span,
fix_info: Option<FixInfo>,
},
}

#[derive(Debug, Clone)]
pub struct FixInfo {
pub suggestion: Option<String>,
pub replacement: Option<String>,
}

Peefy marked this conversation as resolved.
Show resolved Hide resolved
/// A single string error.
pub struct StringError(pub String);

Expand All @@ -357,29 +364,140 @@ impl ParseError {
}

/// New a message parse error with span.
pub fn message(message: String, span: Span) -> Self {
ParseError::Message { message, span }
pub fn message(message: String, span: Span, fix_info: Option<FixInfo>) -> Self {
ParseError::Message {
message,
span,
fix_info,
}
}
}

impl ParseError {
/// Convert a parse error into a error diagnostic.
/// Convert a parse error into an error diagnostic.
pub fn into_diag(self, sess: &Session) -> Result<Diagnostic> {
let span = match self {
ParseError::UnexpectedToken { span, .. } => span,
ParseError::Message { span, .. } => span,
};
let loc = sess.sm.lookup_char_pos(span.lo());
let pos: Position = loc.into();
let suggestions = match self {
ParseError::Message {
fix_info: Some(ref info),
..
} => Some(vec![
info.suggestion
.clone()
.unwrap_or_else(|| "No suggestion available".to_string()),
info.replacement.clone().unwrap_or_else(|| " ".to_string()),
]),
_ => None,
};

let (start_pos, end_pos) = self.generate_modified_range(&self.to_string(), &pos);

Ok(Diagnostic::new_with_code(
Level::Error,
&self.to_string(),
None,
(pos.clone(), pos),
(start_pos, end_pos),
Some(DiagnosticId::Error(ErrorKind::InvalidSyntax)),
None,
suggestions,
))
}

fn generate_modified_range(&self, msg: &str, pos: &Position) -> (Position, Position) {
match msg {
"invalid token '!', consider using 'not '" => {
let start_column = pos.column.unwrap_or(0);
let end_column = start_column + 1;
(
Position {
column: Some(start_column),
..pos.clone()
},
Position {
column: Some(end_column),
..pos.clone()
},
)
}
"'else if' here is invalid in KCL, consider using the 'elif' keyword" => {
let start_column = pos.column.map(|col| col.saturating_sub(5)).unwrap_or(0);
let end_column = pos.column.map(|col| col.saturating_add(2)).unwrap_or(0);
(
Position {
column: Some(start_column),
..pos.clone()
},
Position {
column: Some(end_column),
..pos.clone()
},
)
}
"error nesting on close paren"
| "mismatched closing delimiter"
| "error nesting on close brace" => {
let start_column = pos.column.unwrap_or(0);
let end_column = start_column + 1;
(
Position {
column: Some(start_column),
..pos.clone()
},
Position {
column: Some(end_column),
..pos.clone()
},
)
}
"unterminated string" => {
let start_column = pos.column.unwrap_or(0);
let end_column = start_column + 1;
(
Position {
column: Some(start_column),
..pos.clone()
},
Position {
column: Some(end_column),
..pos.clone()
},
)
}
"unexpected character after line continuation character" => {
let start_column = pos.column.unwrap_or(0);
let end_column = u32::MAX;
(
Position {
column: Some(start_column),
..pos.clone()
},
Position {
column: Some(end_column.into()),
..pos.clone()
},
)
}
"the semicolon ';' here is unnecessary, please remove it" => {
let start_column = pos.column.unwrap_or(0);
let end_column = start_column + 1;
(
Position {
column: Some(start_column),
..pos.clone()
},
Position {
column: Some(end_column),
..pos.clone()
},
)
}
_ => (pos.clone(), pos.clone()),
}
}
}

impl ToString for ParseError {
Expand All @@ -405,7 +523,11 @@ impl SessionDiagnostic for ParseError {
diag.append_component(Box::new(format!(" {}\n", self.to_string())));
Ok(diag)
}
ParseError::Message { message, span } => {
ParseError::Message {
message,
span,
fix_info: _,
} => {
let code_snippet = CodeSnippet::new(span, Arc::clone(&sess.sm));
diag.append_component(Box::new(code_snippet));
diag.append_component(Box::new(format!(" {message}\n")));
Expand Down
70 changes: 52 additions & 18 deletions kclvm/parser/src/lexer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,9 +258,11 @@ impl<'a> Lexer<'a> {
// Unary op
kclvm_lexer::TokenKind::Tilde => token::UnaryOp(token::UTilde),
kclvm_lexer::TokenKind::Bang => {
self.sess.struct_span_error(
self.sess.struct_span_error_with_suggestions(
"invalid token '!', consider using 'not'",
self.span(start, self.pos),
Some("Replace '!' with 'not'".to_string()),
Some("not ".to_string()),
);
token::UnaryOp(token::UNot)
}
Expand Down Expand Up @@ -324,17 +326,21 @@ impl<'a> Lexer<'a> {
token::OpenDelim(token::Paren) => token::CloseDelim(token::Paren),
// error recovery
token::OpenDelim(token::Brace) => {
self.sess.struct_span_error(
self.sess.struct_span_error_with_suggestions(
"error nesting on close paren",
self.span(start, self.pos),
Some("Replace with '}'".to_string()),
Some("}".to_string()),
);
token::CloseDelim(token::Brace)
}
// error recovery
token::OpenDelim(token::Bracket) => {
self.sess.struct_span_error(
self.sess.struct_span_error_with_suggestions(
"error nesting on close paren",
self.span(start, self.pos),
Some("Replace with ']'".to_string()),
Some("]".to_string()),
);
token::CloseDelim(token::Bracket)
}
Expand All @@ -343,9 +349,11 @@ impl<'a> Lexer<'a> {
},
// error recovery
None => {
self.sess.struct_span_error(
self.sess.struct_span_error_with_suggestions(
"error nesting on close paren",
self.span(start, self.pos),
Some("Insert ')'".to_string()),
Some(")".to_string()),
);
token::CloseDelim(token::Paren)
}
Expand All @@ -361,17 +369,21 @@ impl<'a> Lexer<'a> {
token::OpenDelim(token::Brace) => token::CloseDelim(token::Brace),
// error recovery
token::OpenDelim(token::Paren) => {
self.sess.struct_span_error(
self.sess.struct_span_error_with_suggestions(
"error nesting on close brace",
self.span(start, self.pos),
Some("Replace with ')'".to_string()),
Some(")".to_string()),
);
token::CloseDelim(token::Paren)
}
// error recovery
token::OpenDelim(token::Bracket) => {
self.sess.struct_span_error(
self.sess.struct_span_error_with_suggestions(
"error nesting on close brace",
self.span(start, self.pos),
Some("Replace with ']'".to_string()),
Some("]".to_string()),
);
token::CloseDelim(token::Bracket)
}
Expand All @@ -380,9 +392,11 @@ impl<'a> Lexer<'a> {
},
// error recovery
None => {
self.sess.struct_span_error(
self.sess.struct_span_error_with_suggestions(
"error nesting on close brace",
self.span(start, self.pos),
Some("Insert '}'".to_string()),
Some("}".to_string()),
);
token::CloseDelim(token::Brace)
}
Expand All @@ -400,17 +414,21 @@ impl<'a> Lexer<'a> {
token::OpenDelim(token::Bracket) => token::CloseDelim(token::Bracket),
// error recovery
token::OpenDelim(token::Brace) => {
self.sess.struct_span_error(
self.sess.struct_span_error_with_suggestions(
"mismatched closing delimiter",
self.span(start, self.pos),
Some("Replace with '}'".to_string()),
Some("}".to_string()),
);
token::CloseDelim(token::Brace)
}
// error recovery
token::OpenDelim(token::Paren) => {
self.sess.struct_span_error(
self.sess.struct_span_error_with_suggestions(
"mismatched closing delimiter",
self.span(start, self.pos),
Some("Replace with ')'".to_string()),
Some(")".to_string()),
);
token::CloseDelim(token::Paren)
}
Expand All @@ -419,9 +437,11 @@ impl<'a> Lexer<'a> {
},
// error recovery
None => {
self.sess.struct_span_error(
self.sess.struct_span_error_with_suggestions(
"mismatched closing delimiter",
self.span(start, self.pos),
Some("Insert ']'".to_string()),
Some("]".to_string()),
);
token::CloseDelim(token::Bracket)
}
Expand All @@ -430,23 +450,31 @@ impl<'a> Lexer<'a> {
kclvm_lexer::TokenKind::InvalidLineContinue => {
// If we encounter an illegal line continuation character,
// we will restore it to a normal line continuation character.
self.sess.struct_span_error(
self.sess.struct_span_error_with_suggestions(
"unexpected character after line continuation character",
self.span(start, self.pos),
Some("Replace with '\\'".to_string()),
Some("\\".to_string()),
);
return None;
}
kclvm_lexer::TokenKind::Semi => {
// If we encounter an illegal semi token ';', raise a friendly error.
self.sess.struct_span_error(
self.sess.struct_span_error_with_suggestions(
"the semicolon ';' here is unnecessary, please remove it",
self.span(start, self.pos),
Some("Remove ';'".to_string()),
Some(" ".to_string()),
);
return None;
}
_ => {
self.sess
.struct_span_error("unknown start of token", self.span(start, self.pos));
self.sess.struct_span_error_with_suggestions(
"unknown start of token",
self.span(start, self.pos),
Some("Remove unknown token".to_string()),
Some("".to_string()),
);
return None;
}
})
Expand Down Expand Up @@ -503,10 +531,12 @@ impl<'a> Lexer<'a> {
_ => (false, start, start_char),
};
if !terminated {
self.sess.struct_span_error(
self.sess.struct_span_error_with_suggestions(
"unterminated string",
self.span(quote_char_pos, self.pos),
)
Some("Close the string with matching quote".to_string()),
Some("\"".to_string()),
);
}
// Cut offset before validation.
let offset: u32 = if triple_quoted {
Expand Down Expand Up @@ -534,9 +564,11 @@ impl<'a> Lexer<'a> {
let value = if content_start > content_end {
// If get an error string from the eval process,
// directly return an empty string.
self.sess.struct_span_error(
self.sess.struct_span_error_with_suggestions(
"invalid string syntax",
self.span(content_start, self.pos),
Some("Correct the string syntax".to_string()),
Some("\"\"".to_string()),
);
"".to_string()
} else {
Expand All @@ -547,9 +579,11 @@ impl<'a> Lexer<'a> {
None => {
// If get an error string from the eval process,
// directly return an empty string.
self.sess.struct_span_error(
self.sess.struct_span_error_with_suggestions(
"invalid string syntax",
self.span(content_start, self.pos),
Some("Correct the string syntax".to_string()),
Some("\"\"".to_string()),
);
"".to_string()
}
Expand Down
4 changes: 3 additions & 1 deletion kclvm/parser/src/parser/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -599,9 +599,11 @@ impl<'a> Parser<'a> {

// `else if -> elif` error recovery.
if self.token.is_keyword(kw::If) {
self.sess.struct_span_error(
self.sess.struct_span_error_with_suggestions(
"'else if' here is invalid in KCL, consider using the 'elif' keyword",
self.token.span,
Some("Use 'elif' instead of 'else if'".to_string()),
Some("elif".to_string()),
);
} else if self.token.kind != TokenKind::Colon {
self.sess
Expand Down
Loading
Loading