]> git.lizzy.rs Git - rust.git/commitdiff
Rollup merge of #99030 - rust-lang:notriddle/field-recovery, r=petrochenkov
authorDylan DPC <99973273+Dylan-DPC@users.noreply.github.com>
Wed, 13 Jul 2022 14:02:35 +0000 (19:32 +0530)
committerGitHub <noreply@github.com>
Wed, 13 Jul 2022 14:02:35 +0000 (19:32 +0530)
diagnostics: error messages when struct literals fail to parse

If an expression is supplied where a field is expected, the parser can become convinced that it's a shorthand field syntax when it's not.

This PR addresses it by explicitly recording the permitted `:` token immediately after the identifier, and also adds a suggestion to insert the name of the field if it looks like a complex expression.

Fixes #98917

1  2 
compiler/rustc_parse/src/parser/expr.rs

index f9387e29262ae19684d8425f281ed78ae534c9dd,52fefa2ebe590c7ddd3211b3d286d12cd97af136..0d9c57908c1e3620d12eee09236ea06f83fed65b
@@@ -1393,9 -1393,7 +1393,9 @@@ impl<'a> Parser<'a> 
              self.parse_yield_expr(attrs)
          } else if self.is_do_yeet() {
              self.parse_yeet_expr(attrs)
 -        } else if self.eat_keyword(kw::Let) {
 +        } else if self.check_keyword(kw::Let) {
 +            self.manage_let_chains_context();
 +            self.bump();
              self.parse_let_expr(attrs)
          } else if self.eat_keyword(kw::Underscore) {
              Ok(self.mk_expr(self.prev_token.span, ExprKind::Underscore, attrs))
          Ok(cond)
      }
  
 +    // Checks if `let` is in an invalid position like `let x = let y = 1;` or
 +    // if the current `let` is in a let_chains context but nested in another
 +    // expression like `if let Some(_) = _opt && [1, 2, 3][let _ = ()] = 1`.
 +    //
 +    // This method expects that the current token is `let`.
 +    fn manage_let_chains_context(&mut self) {
 +        debug_assert!(matches!(self.token.kind, TokenKind::Ident(kw::Let, _)));
 +        let is_in_a_let_chains_context_but_nested_in_other_expr = self.let_expr_allowed
 +            && !matches!(
 +                self.prev_token.kind,
 +                TokenKind::AndAnd
 +                    | TokenKind::CloseDelim(Delimiter::Brace)
 +                    | TokenKind::Ident(kw::If, _)
 +                    | TokenKind::Ident(kw::While, _)
 +            );
 +        if !self.let_expr_allowed || is_in_a_let_chains_context_but_nested_in_other_expr {
 +            self.struct_span_err(self.token.span, "expected expression, found `let` statement")
 +                .emit();
 +        }
 +    }
 +
      /// Parses a `let $pat = $expr` pseudo-expression.
      /// The `let` token has already been eaten.
      fn parse_let_expr(&mut self, attrs: AttrVec) -> PResult<'a, P<Expr>> {
 -        if !self.let_expr_allowed {
 -            self.struct_span_err(
 -                self.prev_token.span,
 -                "expected expression, found `let` statement",
 -            )
 -            .emit();
 -        }
          let lo = self.prev_token.span;
          let pat = self.parse_pat_allow_top_alt(
              None,
                  }
              };
  
+             let is_shorthand = parsed_field.as_ref().map_or(false, |f| f.is_shorthand);
+             // A shorthand field can be turned into a full field with `:`.
+             // We should point this out.
+             self.check_or_expected(!is_shorthand, TokenType::Token(token::Colon));
              match self.expect_one_of(&[token::Comma], &[token::CloseDelim(close_delim)]) {
                  Ok(_) => {
                      if let Some(f) = parsed_field.or(recovery_field) {
                                  ",",
                                  Applicability::MachineApplicable,
                              );
+                         } else if is_shorthand
+                             && (AssocOp::from_token(&self.token).is_some()
+                                 || matches!(&self.token.kind, token::OpenDelim(_))
+                                 || self.token.kind == token::Dot)
+                         {
+                             // Looks like they tried to write a shorthand, complex expression.
+                             let ident = parsed_field.expect("is_shorthand implies Some").ident;
+                             e.span_suggestion(
+                                 ident.span.shrink_to_lo(),
+                                 "try naming a field",
+                                 &format!("{ident}: "),
+                                 Applicability::HasPlaceholders,
+                             );
                          }
                      }
                      if !recover {