Skip to content

Commit

Permalink
Parser: recovery for auto-completion (FuelLabs#2858)
Browse files Browse the repository at this point in the history
Recovers on `expr.`

Fixes FuelLabs#2854.

Co-authored-by: Joshua Batty <[email protected]>
  • Loading branch information
Centril and JoshuaBatty authored Sep 28, 2022
1 parent f9ebfba commit 40c708f
Show file tree
Hide file tree
Showing 12 changed files with 59 additions and 6 deletions.
5 changes: 5 additions & 0 deletions sway-ast/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ pub mod op_code;

#[derive(Clone, Debug)]
pub enum Expr {
/// A malformed expression.
///
/// Used for parser recovery when we cannot form a more specific node.
Error(Box<[Span]>),
Path(PathExpr),
Literal(Literal),
AbiCast {
Expand Down Expand Up @@ -179,6 +183,7 @@ pub enum Expr {
impl Spanned for Expr {
fn span(&self) -> Span {
match self {
Expr::Error(spans) => spans.iter().cloned().reduce(Span::join).unwrap(),
Expr::Path(path_expr) => path_expr.span(),
Expr::Literal(literal) => literal.span(),
Expr::AbiCast { abi_token, args } => Span::join(abi_token.span(), args.span()),
Expand Down
4 changes: 4 additions & 0 deletions sway-core/src/convert_parse_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1538,6 +1538,10 @@ fn expr_func_app_to_expression_kind(
fn expr_to_expression(ec: &mut ErrorContext, expr: Expr) -> Result<Expression, ErrorEmitted> {
let span = expr.span();
let expression = match expr {
Expr::Error(part_spans) => Expression {
kind: ExpressionKind::Error(part_spans),
span,
},
Expr::Path(path_expr) => path_expr_to_expression(ec, path_expr)?,
Expr::Literal(literal) => Expression {
kind: ExpressionKind::Literal(literal_to_literal(ec, literal)?),
Expand Down
6 changes: 6 additions & 0 deletions sway-core/src/parse_tree/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ pub struct ReassignmentExpression {

#[derive(Debug, Clone)]
pub enum ExpressionKind {
/// A malformed expression.
///
/// Used for parser recovery when we cannot form a more specific node.
/// The list of `Span`s are for consumption by the LSP and are,
/// when joined, the same as that stored in `expr.span`.
Error(Box<[Span]>),
Literal(Literal),
FunctionApplication(Box<FunctionApplicationExpression>),
LazyOperator(LazyOperatorExpression),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,8 @@ impl TypedExpression {
let expr_span = expr.span();
let span = expr_span.clone();
let res = match expr.kind {
// We've already emitted an error for the `::Error` case.
ExpressionKind::Error(_) => ok(error_recovery_expr(span), vec![], vec![]),
ExpressionKind::Literal(lit) => Self::type_check_literal(lit, span),
ExpressionKind::Variable(name) => {
Self::type_check_variable_expression(ctx.namespace, name, span)
Expand Down
10 changes: 6 additions & 4 deletions sway-core/src/semantic_analysis/node_dependencies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,22 +479,24 @@ impl Dependencies {
self.gather_from_call_path(&abi_cast_expression.abi_name, false, false)
}

ExpressionKind::Literal(_) => self,
ExpressionKind::Literal(_)
| ExpressionKind::Break
| ExpressionKind::Continue
| ExpressionKind::StorageAccess(_)
| ExpressionKind::Error(_) => self,

ExpressionKind::Tuple(fields) => {
self.gather_from_iter(fields.iter(), |deps, field| deps.gather_from_expr(field))
}
ExpressionKind::TupleIndex(TupleIndexExpression { prefix, .. }) => {
self.gather_from_expr(prefix)
}
ExpressionKind::StorageAccess(_) => self,
ExpressionKind::IntrinsicFunction(IntrinsicFunctionExpression {
arguments, ..
}) => self.gather_from_iter(arguments.iter(), |deps, arg| deps.gather_from_expr(arg)),
ExpressionKind::WhileLoop(WhileLoopExpression {
condition, body, ..
}) => self.gather_from_expr(condition).gather_from_block(body),
ExpressionKind::Break => self,
ExpressionKind::Continue => self,
ExpressionKind::Reassignment(reassignment) => self.gather_from_expr(&reassignment.rhs),
ExpressionKind::Return(expr) => self.gather_from_expr(expr),
}
Expand Down
5 changes: 4 additions & 1 deletion sway-lsp/src/core/traverse_parse_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,9 @@ fn handle_declaration(declaration: &Declaration, tokens: &TokenMap) {
fn handle_expression(expression: &Expression, tokens: &TokenMap) {
let span = &expression.span;
match &expression.kind {
ExpressionKind::Error(_part_spans) => {
// FIXME(Centril): Left for @JoshuaBatty to use.
}
ExpressionKind::Literal(value) => {
let symbol_kind = literal_to_symbol_kind(value);

Expand Down Expand Up @@ -567,7 +570,7 @@ fn handle_expression(expression: &Expression, tokens: &TokenMap) {
body, condition, ..
}) => handle_while_loop(body, condition, tokens),
// TODO: collect these tokens as keywords once the compiler returns the span
ExpressionKind::Break | ExpressionKind::Continue => (),
ExpressionKind::Break | ExpressionKind::Continue => {}
ExpressionKind::Reassignment(reassignment) => {
handle_expression(&reassignment.rhs, tokens);

Expand Down
10 changes: 9 additions & 1 deletion sway-parse/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,8 @@ fn parse_projection(parser: &mut Parser, ctx: ParseExprCtx) -> ParseResult<Expr>
}
if let Some(dot_token) = parser.take() {
let target = Box::new(expr);

// Try parsing a field access or a method call.
if let Some(name) = parser.take() {
if !ctx.parsing_conditional {
if let Some(contract_args) = Braces::try_parse(parser)? {
Expand Down Expand Up @@ -526,6 +528,8 @@ fn parse_projection(parser: &mut Parser, ctx: ParseExprCtx) -> ParseResult<Expr>
};
continue;
}

// Try parsing a tuple field projection.
if let Some(lit) = parser.take() {
let lit_int = match lit {
Literal::Int(lit_int) => lit_int,
Expand Down Expand Up @@ -555,7 +559,11 @@ fn parse_projection(parser: &mut Parser, ctx: ParseExprCtx) -> ParseResult<Expr>
};
continue;
}
return Err(parser.emit_error(ParseErrorKind::ExpectedFieldName));

// Nothing expected followed. Now we have parsed `expr .`.
// Try to recover as an unknown sort of expression.
parser.emit_error(ParseErrorKind::ExpectedFieldName);
return Ok(Expr::Error([target.span(), dot_token.span()].into()));
}
return Ok(expr);
}
Expand Down
2 changes: 2 additions & 0 deletions swayfmt/src/utils/language/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ impl Format for Expr {
formatter: &mut Formatter,
) -> Result<(), FormatterError> {
match self {
Self::Error(_) => {}
Self::Path(path) => path.format(formatted_code, formatter)?,
Self::Literal(lit) => lit.format(formatted_code, formatter)?,
Self::AbiCast { abi_token, args } => {
Expand Down Expand Up @@ -722,6 +723,7 @@ impl LeafSpans for Expr {
/// Collects various expr field's ByteSpans.
fn visit_expr(expr: &Expr) -> Vec<ByteSpan> {
match expr {
Expr::Error(_) => vec![expr.span().into()],
Expr::Path(path) => path.leaf_spans(),
Expr::Literal(literal) => literal.leaf_spans(),
Expr::AbiCast { abi_token, args } => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[[package]]
name = 'recover_expr_dot'
source = 'root'
dependencies = []
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[project]
name = "recover_expr_dot"
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
implicit-std = false
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
contract;

fn recover_on_expr_dot() {
foo.;
}

fn recovery_witness() -> bool { 0 }
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
category = "fail"

# check: $()Expected a field name.
# check: $()Mismatched types.

0 comments on commit 40c708f

Please sign in to comment.