Repository: rust-lang/rust-analyzer
Pull request #17536 (2024-07-03 09:00:29)

fix: Don't emit semantic diagnostics in files with a lot of syntax errors
These will only add to the noise when something very unexpected breaks or where parser recovery fails to kick in. 


Patch:
diff --git a/crates/hir-expand/src/declarative.rs b/crates/hir-expand/src/declarative.rs
index 29408902f1..7c3bf995b1 100644
--- a/crates/hir-expand/src/declarative.rs
+++ b/crates/hir-expand/src/declarative.rs
@@ -172,30 +172,15 @@ impl DeclarativeMacroExpander {
             ),
             ast::Macro::MacroDef(macro_def) => (
                 match macro_def.body() {
-                    Some(body) => {
-                        let span =
-                            map.span_for_range(macro_def.macro_token().unwrap().text_range());
-                        let args = macro_def.args().map(|args| {
-                            mbe::syntax_node_to_token_tree(
-                                args.syntax(),
-                                map.as_ref(),
-                                span,
-                                DocCommentDesugarMode::Mbe,
-                            )
-                        });
-                        let body = mbe::syntax_node_to_token_tree(
-                            body.syntax(),
+                    Some(arg) => {
+                        let tt = mbe::syntax_node_to_token_tree(
+                            arg.syntax(),
                             map.as_ref(),
-                            span,
+                            map.span_for_range(macro_def.macro_token().unwrap().text_range()),
                             DocCommentDesugarMode::Mbe,
                         );
 
-                        mbe::DeclarativeMacro::parse_macro2(
-                            args.as_ref(),
-                            &body,
-                            edition,
-                            new_meta_vars,
-                        )
+                        mbe::DeclarativeMacro::parse_macro2(&tt, edition, new_meta_vars)
                     }
                     None => mbe::DeclarativeMacro::from_err(mbe::ParseError::Expected(
                         "expected a token tree".into(),
diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs
index 2bf3f5f0ce..eb8e8e91d0 100644
--- a/crates/ide-diagnostics/src/lib.rs
+++ b/crates/ide-diagnostics/src/lib.rs
@@ -311,9 +311,13 @@ pub fn diagnostics(
             FileRange { file_id, range: err.range() },
         )
     }));
+    let parse_errors = res.len();
 
     let parse = sema.parse(file_id);
 
+    // FIXME: This iterates the entire file which is a rather expensive operation.
+    // We should implement these differently in some form?
+    // Salsa caching + incremental re-parse would be better here
     for node in parse.syntax().descendants() {
         handlers::useless_braces::useless_braces(&mut res, file_id, &node);
         handlers::field_shorthand::field_shorthand(&mut res, file_id, &node);
@@ -326,7 +330,10 @@ pub fn diagnostics(
 
     let mut diags = Vec::new();
     match module {
-        Some(m) => m.diagnostics(db, &mut diags, config.style_lints),
+        // A bunch of parse errors in a file indicate some bigger structural parse changes in the
+        // file, so we skip semantic diagnostics so we can show these faster.
+        Some(m) if parse_errors < 16 => m.diagnostics(db, &mut diags, config.style_lints),
+        Some(_) => (),
         None => handlers::unlinked_file::unlinked_file(&ctx, &mut res, file_id),
     }
 
diff --git a/crates/mbe/src/lib.rs b/crates/mbe/src/lib.rs
index b06c6cee12..b2fd91f6ce 100644
--- a/crates/mbe/src/lib.rs
+++ b/crates/mbe/src/lib.rs
@@ -156,7 +156,7 @@ impl DeclarativeMacro {
         let mut err = None;
 
         while src.len() > 0 {
-            let rule = match Rule::parse(edition, &mut src, new_meta_vars) {
+            let rule = match Rule::parse(edition, &mut src, true, new_meta_vars) {
                 Ok(it) => it,
                 Err(e) => {
                     err = Some(Box::new(e));
@@ -184,34 +184,19 @@ impl DeclarativeMacro {
 
     /// The new, unstable `macro m {}` flavor.
     pub fn parse_macro2(
-        args: Option<&tt::Subtree<Span>>,
-        body: &tt::Subtree<Span>,
+        tt: &tt::Subtree<Span>,
         edition: impl Copy + Fn(SyntaxContextId) -> Edition,
         // FIXME: Remove this once we drop support for rust 1.76 (defaults to true then)
         new_meta_vars: bool,
     ) -> DeclarativeMacro {
+        let mut src = TtIter::new(tt);
         let mut rules = Vec::new();
         let mut err = None;
 
-        if let Some(args) = args {
-            cov_mark::hit!(parse_macro_def_simple);
-
-            let rule = (|| {
-                let lhs = MetaTemplate::parse_pattern(edition, args)?;
-                let rhs = MetaTemplate::parse_template(edition, body, new_meta_vars)?;
-
-                Ok(crate::Rule { lhs, rhs })
-            })();
-
-            match rule {
-                Ok(rule) => rules.push(rule),
-                Err(e) => err = Some(Box::new(e)),
-            }
-        } else {
+        if tt::DelimiterKind::Brace == tt.delimiter.kind {
             cov_mark::hit!(parse_macro_def_rules);
-            let mut src = TtIter::new(body);
             while src.len() > 0 {
-                let rule = match Rule::parse(edition, &mut src, new_meta_vars) {
+                let rule = match Rule::parse(edition, &mut src, true, new_meta_vars) {
                     Ok(it) => it,
                     Err(e) => {
                         err = Some(Box::new(e));
@@ -228,6 +213,19 @@ impl DeclarativeMacro {
                     break;
                 }
             }
+        } else {
+            cov_mark::hit!(parse_macro_def_simple);
+            match Rule::parse(edition, &mut src, false, new_meta_vars) {
+                Ok(rule) => {
+                    if src.len() != 0 {
+                        err = Some(Box::new(ParseError::expected("remaining tokens in macro def")));
+                    }
+                    rules.push(rule);
+                }
+                Err(e) => {
+                    err = Some(Box::new(e));
+                }
+            }
         }
 
         for Rule { lhs, .. } in &rules {
@@ -264,11 +262,14 @@ impl Rule {
     fn parse(
         edition: impl Copy + Fn(SyntaxContextId) -> Edition,
         src: &mut TtIter<'_, Span>,
+        expect_arrow: bool,
         new_meta_vars: bool,
     ) -> Result<Self, ParseError> {
         let lhs = src.expect_subtree().map_err(|()| ParseError::expected("expected subtree"))?;
-        src.expect_char('=').map_err(|()| ParseError::expected("expected `=`"))?;
-        src.expect_char('>').map_err(|()| ParseError::expected("expected `>`"))?;
+        if expect_arrow {
+            src.expect_char('=').map_err(|()| ParseError::expected("expected `=`"))?;
+            src.expect_char('>').map_err(|()| ParseError::expected("expected `>`"))?;
+        }
         let rhs = src.expect_subtree().map_err(|()| ParseError::expected("expected subtree"))?;
 
         let lhs = MetaTemplate::parse_pattern(edition, lhs)?;
diff --git a/crates/parser/src/grammar/items.rs b/crates/parser/src/grammar/items.rs
index 99bbf47654..25c00ccf5f 100644
--- a/crates/parser/src/grammar/items.rs
+++ b/crates/parser/src/grammar/items.rs
@@ -372,11 +372,13 @@ fn macro_def(p: &mut Parser<'_>, m: Marker) {
         // macro m { ($i:ident) => {} }
         token_tree(p);
     } else if p.at(T!['(']) {
+        let m = p.start();
         token_tree(p);
         match p.current() {
             T!['{'] | T!['['] | T!['('] => token_tree(p),
             _ => p.error("expected `{`, `[`, `(`"),
         }
+        m.complete(p, TOKEN_TREE);
     } else {
         p.error("unmatched `(`");
     }
diff --git a/crates/syntax/src/ast/expr_ext.rs b/crates/syntax/src/ast/expr_ext.rs
index b0ee9dfd50..28a9dadace 100644
--- a/crates/syntax/src/ast/expr_ext.rs
+++ b/crates/syntax/src/ast/expr_ext.rs
@@ -6,8 +6,7 @@ use crate::{
     ast::{
         self,
         operators::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, UnaryOp},
-        support, ArgList, AstChildren, AstNode, BlockExpr, ClosureExpr, Const, Expr, Fn,
-        FormatArgsArg, FormatArgsExpr, MacroDef, Static, TokenTree,
+        support, AstChildren, AstNode,
     },
     AstToken,
     SyntaxKind::*,
@@ -436,57 +435,3 @@ impl AstNode for CallableExpr {
         }
     }
 }
-
-impl MacroDef {
-    fn tts(&self) -> (Option<ast::TokenTree>, Option<ast::TokenTree>) {
-        let mut types = support::children(self.syntax());
-        let first = types.next();
-        let second = types.next();
-        (first, second)
-    }
-
-    pub fn args(&self) -> Option<TokenTree> {
-        match self.tts() {
-            (Some(args), Some(_)) => Some(args),
-            _ => None,
-        }
-    }
-
-    pub fn body(&self) -> Option<TokenTree> {
-        match self.tts() {
-            (Some(body), None) | (_, Some(body)) => Some(body),
-            _ => None,
-        }
-    }
-}
-
-impl ClosureExpr {
-    pub fn body(&self) -> Option<Expr> {
-        support::child(&self.syntax)
-    }
-}
-impl Const {
-    pub fn body(&self) -> Option<Expr> {
-        support::child(&self.syntax)
-    }
-}
-impl Fn {
-    pub fn body(&self) -> Option<BlockExpr> {
-        support::child(&self.syntax)
-    }
-}
-impl Static {
-    pub fn body(&self) -> Option<Expr> {
-        support::child(&self.syntax)
-    }
-}
-impl FormatArgsExpr {
-    pub fn args(&self) -> AstChildren<FormatArgsArg> {
-        support::children(&self.syntax)
-    }
-}
-impl ArgList {
-    pub fn args(&self) -> AstChildren<Expr> {
-        support::children(&self.syntax)
-    }
-}
diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs
index bae529a2c8..98186c5473 100644
--- a/crates/syntax/src/ast/generated/nodes.rs
+++ b/crates/syntax/src/ast/generated/nodes.rs
@@ -20,6 +20,7 @@ pub struct ArgList {
     pub(crate) syntax: SyntaxNode,
 }
 impl ArgList {
+    pub fn args(&self) -> AstChildren<Expr> { support::children(&self.syntax) }
     pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
     pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
 }
@@ -190,6 +191,7 @@ pub struct ClosureExpr {
 }
 impl ast::HasAttrs for ClosureExpr {}
 impl ClosureExpr {
+    pub fn body(&self) -> Option<Expr> { support::child(&self.syntax) }
     pub fn generic_param_list(&self) -> Option<GenericParamList> { support::child(&self.syntax) }
     pub fn param_list(&self) -> Option<ParamList> { support::child(&self.syntax) }
     pub fn ret_type(&self) -> Option<RetType> { support::child(&self.syntax) }
@@ -209,6 +211,7 @@ impl ast::HasDocComments for Const {}
 impl ast::HasName for Const {}
 impl ast::HasVisibility for Const {}
 impl Const {
+    pub fn body(&self) -> Option<Expr> { support::child(&self.syntax) }
     pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) }
     pub fn colon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![:]) }
     pub fn semicolon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![;]) }
@@ -353,6 +356,7 @@ impl ast::HasName for Fn {}
 impl ast::HasVisibility for Fn {}
 impl Fn {
     pub fn abi(&self) -> Option<Abi> { support::child(&self.syntax) }
+    pub fn body(&self) -> Option<BlockExpr> { support::child(&self.syntax) }
     pub fn param_list(&self) -> Option<ParamList> { support::child(&self.syntax) }
     pub fn ret_type(&self) -> Option<RetType> { support::child(&self.syntax) }
     pub fn semicolon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![;]) }
@@ -414,6 +418,7 @@ pub struct FormatArgsExpr {
 }
 impl ast::HasAttrs for FormatArgsExpr {}
 impl FormatArgsExpr {
+    pub fn args(&self) -> AstChildren<FormatArgsArg> { support::children(&self.syntax) }
     pub fn template(&self) -> Option<Expr> { support::child(&self.syntax) }
     pub fn pound_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![#]) }
     pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
@@ -647,6 +652,8 @@ impl ast::HasDocComments for MacroDef {}
 impl ast::HasName for MacroDef {}
 impl ast::HasVisibility for MacroDef {}
 impl MacroDef {
+    pub fn args(&self) -> Option<TokenTree> { support::child(&self.syntax) }
+    pub fn body(&self) -> Option<TokenTree> { support::child(&self.syntax) }
     pub fn macro_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![macro]) }
 }
 
@@ -1217,6 +1224,7 @@ impl ast::HasDocComments for Static {}
 impl ast::HasName for Static {}
 impl ast::HasVisibility for Static {}
 impl Static {
+    pub fn body(&self) -> Option<Expr> { support::child(&self.syntax) }
     pub fn ty(&self) -> Option<Type> { support::child(&self.syntax) }
     pub fn colon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![:]) }
     pub fn semicolon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![;]) }
diff --git a/xtask/src/codegen/grammar.rs b/xtask/src/codegen/grammar.rs
index 0fc97ad654..cc2fadc975 100644
--- a/xtask/src/codegen/grammar.rs
+++ b/xtask/src/codegen/grammar.rs
@@ -692,8 +692,6 @@ fn lower_rule(acc: &mut Vec<Field>, grammar: &Grammar, label: Option<&String>, r
                     | "self_ty"
                     | "iterable"
                     | "condition"
-                    | "args"
-                    | "body"
             );
             if manually_implemented {
                 return;


Test patch:
diff --git a/crates/parser/test_data/parser/inline/ok/0147_macro_def.rast b/crates/parser/test_data/parser/inline/ok/0147_macro_def.rast
index f73229b2e3..01de13a907 100644
--- a/crates/parser/test_data/parser/inline/ok/0147_macro_def.rast
+++ b/crates/parser/test_data/parser/inline/ok/0147_macro_def.rast
@@ -5,14 +5,15 @@ SOURCE_FILE
     NAME
       IDENT "m"
     TOKEN_TREE
-      L_PAREN "("
-      DOLLAR "$"
-      IDENT "i"
-      COLON ":"
-      IDENT "ident"
-      R_PAREN ")"
-    WHITESPACE " "
-    TOKEN_TREE
-      L_CURLY "{"
-      R_CURLY "}"
+      TOKEN_TREE
+        L_PAREN "("
+        DOLLAR "$"
+        IDENT "i"
+        COLON ":"
+        IDENT "ident"
+        R_PAREN ")"
+      WHITESPACE " "
+      TOKEN_TREE
+        L_CURLY "{"
+        R_CURLY "}"
   WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/ok/0012_visibility.rast b/crates/parser/test_data/parser/ok/0012_visibility.rast
index 3d9322947b..a95bc23016 100644
--- a/crates/parser/test_data/parser/ok/0012_visibility.rast
+++ b/crates/parser/test_data/parser/ok/0012_visibility.rast
@@ -39,15 +39,16 @@ SOURCE_FILE
     NAME
       IDENT "m"
     TOKEN_TREE
-      L_PAREN "("
-      DOLLAR "$"
-      COLON ":"
-      IDENT "ident"
-      R_PAREN ")"
-    WHITESPACE " "
-    TOKEN_TREE
-      L_CURLY "{"
-      R_CURLY "}"
+      TOKEN_TREE
+        L_PAREN "("
+        DOLLAR "$"
+        COLON ":"
+        IDENT "ident"
+        R_PAREN ")"
+      WHITESPACE " "
+      TOKEN_TREE
+        L_CURLY "{"
+        R_CURLY "}"
   WHITESPACE "\n"
   FN
     VISIBILITY
diff --git a/crates/parser/test_data/parser/ok/0062_macro_2.0.rast b/crates/parser/test_data/parser/ok/0062_macro_2.0.rast
index 1415a866b6..3915ed7506 100644
--- a/crates/parser/test_data/parser/ok/0062_macro_2.0.rast
+++ b/crates/parser/test_data/parser/ok/0062_macro_2.0.rast
@@ -5,50 +5,51 @@ SOURCE_FILE
     NAME
       IDENT "parse_use_trees"
     TOKEN_TREE
-      L_PAREN "("
-      DOLLAR "$"
       TOKEN_TREE
         L_PAREN "("
         DOLLAR "$"
-        IDENT "s"
-        COLON ":"
-        IDENT "expr"
-        R_PAREN ")"
-      COMMA ","
-      STAR "*"
-      WHITESPACE " "
-      DOLLAR "$"
-      TOKEN_TREE
-        L_PAREN "("
+        TOKEN_TREE
+          L_PAREN "("
+          DOLLAR "$"
+          IDENT "s"
+          COLON ":"
+          IDENT "expr"
+          R_PAREN ")"
         COMMA ","
-        R_PAREN ")"
-      STAR "*"
-      R_PAREN ")"
-    WHITESPACE " "
-    TOKEN_TREE
-      L_CURLY "{"
-      WHITESPACE "\n    "
-      IDENT "vec"
-      BANG "!"
-      TOKEN_TREE
-        L_BRACK "["
-        WHITESPACE "\n        "
+        STAR "*"
+        WHITESPACE " "
         DOLLAR "$"
         TOKEN_TREE
           L_PAREN "("
-          IDENT "parse_use_tree"
-          TOKEN_TREE
-            L_PAREN "("
-            DOLLAR "$"
-            IDENT "s"
-            R_PAREN ")"
           COMMA ","
           R_PAREN ")"
         STAR "*"
+        R_PAREN ")"
+      WHITESPACE " "
+      TOKEN_TREE
+        L_CURLY "{"
         WHITESPACE "\n    "
-        R_BRACK "]"
-      WHITESPACE "\n"
-      R_CURLY "}"
+        IDENT "vec"
+        BANG "!"
+        TOKEN_TREE
+          L_BRACK "["
+          WHITESPACE "\n        "
+          DOLLAR "$"
+          TOKEN_TREE
+            L_PAREN "("
+            IDENT "parse_use_tree"
+            TOKEN_TREE
+              L_PAREN "("
+              DOLLAR "$"
+              IDENT "s"
+              R_PAREN ")"
+            COMMA ","
+            R_PAREN ")"
+          STAR "*"
+          WHITESPACE "\n    "
+          R_BRACK "]"
+        WHITESPACE "\n"
+        R_CURLY "}"
   WHITESPACE "\n\n"
   FN
     ATTR
@@ -79,62 +80,79 @@ SOURCE_FILE
           NAME
             IDENT "test_merge"
           TOKEN_TREE
-            L_PAREN "("
             TOKEN_TREE
-              L_BRACK "["
-              DOLLAR "$"
+              L_PAREN "("
               TOKEN_TREE
-                L_PAREN "("
+                L_BRACK "["
                 DOLLAR "$"
-                IDENT "input"
-                COLON ":"
-                IDENT "expr"
-                R_PAREN ")"
-              COMMA ","
-              STAR "*"
-              WHITESPACE " "
-              DOLLAR "$"
-              TOKEN_TREE
-                L_PAREN "("
+                TOKEN_TREE
+                  L_PAREN "("
+                  DOLLAR "$"
+                  IDENT "input"
+                  COLON ":"
+                  IDENT "expr"
+                  R_PAREN ")"
                 COMMA ","
-                R_PAREN ")"
-              STAR "*"
-              R_BRACK "]"
-            COMMA ","
-            WHITESPACE " "
-            TOKEN_TREE
-              L_BRACK "["
-              DOLLAR "$"
-              TOKEN_TREE
-                L_PAREN "("
+                STAR "*"
+                WHITESPACE " "
                 DOLLAR "$"
-                IDENT "output"
-                COLON ":"
-                IDENT "expr"
-                R_PAREN ")"
+                TOKEN_TREE
+                  L_PAREN "("
+                  COMMA ","
+                  R_PAREN ")"
+                STAR "*"
+                R_BRACK "]"
               COMMA ","
-              STAR "*"
               WHITESPACE " "
-              DOLLAR "$"
               TOKEN_TREE
-                L_PAREN "("
+                L_BRACK "["
+                DOLLAR "$"
+                TOKEN_TREE
+                  L_PAREN "("
+                  DOLLAR "$"
+                  IDENT "output"
+                  COLON ":"
+                  IDENT "expr"
+                  R_PAREN ")"
                 COMMA ","
-                R_PAREN ")"
-              STAR "*"
-              R_BRACK "]"
-            R_PAREN ")"
-          WHITESPACE " "
-          TOKEN_TREE
-            L_CURLY "{"
-            WHITESPACE "\n        "
-            IDENT "assert_eq"
-            BANG "!"
+                STAR "*"
+                WHITESPACE " "
+                DOLLAR "$"
+                TOKEN_TREE
+                  L_PAREN "("
+                  COMMA ","
+                  R_PAREN ")"
+                STAR "*"
+                R_BRACK "]"
+              R_PAREN ")"
+            WHITESPACE " "
             TOKEN_TREE
-              L_PAREN "("
-              WHITESPACE "\n            "
-              IDENT "merge_use_trees"
+              L_CURLY "{"
+              WHITESPACE "\n        "
+              IDENT "assert_eq"
+              BANG "!"
               TOKEN_TREE
                 L_PAREN "("
+                WHITESPACE "\n            "
+                IDENT "merge_use_trees"
+                TOKEN_TREE
+                  L_PAREN "("
+                  IDENT "parse_use_trees"
+                  BANG "!"
+                  TOKEN_TREE
+                    L_PAREN "("
+                    DOLLAR "$"
+                    TOKEN_TREE
+                      L_PAREN "("
+                      DOLLAR "$"
+                      IDENT "input"
+                      COMMA ","
+                      R_PAREN ")"
+                    STAR "*"
+                    R_PAREN ")"
+                  R_PAREN ")"
+                COMMA ","
+                WHITESPACE "\n            "
                 IDENT "parse_use_trees"
                 BANG "!"
                 TOKEN_TREE
@@ -143,33 +161,17 @@ SOURCE_FILE
                   TOKEN_TREE
                     L_PAREN "("
                     DOLLAR "$"
-                    IDENT "input"
+                    IDENT "output"
                     COMMA ","
                     R_PAREN ")"
                   STAR "*"
                   R_PAREN ")"
+                COMMA ","
+                WHITESPACE "\n        "
                 R_PAREN ")"
-              COMMA ","
-              WHITESPACE "\n            "
-              IDENT "parse_use_trees"
-              BANG "!"
-              TOKEN_TREE
-                L_PAREN "("
-                DOLLAR "$"
-                TOKEN_TREE
-                  L_PAREN "("
-                  DOLLAR "$"
-                  IDENT "output"
-                  COMMA ","
-                  R_PAREN ")"
-                STAR "*"
-                R_PAREN ")"
-              COMMA ","
-              WHITESPACE "\n        "
-              R_PAREN ")"
-            SEMICOLON ";"
-            WHITESPACE "\n    "
-            R_CURLY "}"
+              SEMICOLON ";"
+              WHITESPACE "\n    "
+              R_CURLY "}"
         WHITESPACE "\n"
         R_CURLY "}"
   WHITESPACE "\n"