Skip to content

Commit

Permalink
Provide quick fix for invalid syntax error (#1133)
Browse files Browse the repository at this point in the history
* added quick fix for multiple assignments

Signed-off-by: Shashank Mittal <shashank.mittal.mec22@itbhu.ac.in>

* fix

Signed-off-by: Shashank Mittal <shashank.mittal.mec22@itbhu.ac.in>

* expanded ParseError enum

Signed-off-by: Shashank Mittal <shashank.mittal.mec22@itbhu.ac.in>

* new structure introduced

Signed-off-by: Shashank Mittal <shashank.mittal.mec22@itbhu.ac.in>

* some quick fixes completed

Signed-off-by: Shashank Mittal <shashank.mittal.mec22@itbhu.ac.in>

* changes implemented

Signed-off-by: Shashank Mittal <shashank.mittal.mec22@itbhu.ac.in>

* fmt

Signed-off-by: Shashank Mittal <shashank.mittal.mec22@itbhu.ac.in>

---------

Signed-off-by: Shashank Mittal <shashank.mittal.mec22@itbhu.ac.in>
  • Loading branch information
shashank-iitbhu committed May 25, 2024
1 parent 8d53ec0 commit fd4967e
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 26 deletions.
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>,
}

/// 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

0 comments on commit fd4967e

Please sign in to comment.