Repository: rust-analyzer/rust-analyzer
Pull request #11238 (2022-01-08 14:40:13)

fix: shrink the span of errors from attribute macros and derives
Some procedural macros tend to get very large invocations, for example RTIC's, leading to issues like https://github.com/rtic-rs/cortex-m-rtic/issues/582, where almost the entire screen is underlined while editing incomplete code in the macro.

This PR shrinks the spans of errors from attribute macros and derives to point only at the attribute, which also matches rustc more closely.

bors r+


Patch:
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs
index 5f190b8727..5e49d3f6af 100644
--- a/crates/hir_expand/src/lib.rs
+++ b/crates/hir_expand/src/lib.rs
@@ -350,6 +350,53 @@ impl MacroCallKind {
         }
     }
 
+    /// Returns the original file range that best describes the location of this macro call.
+    ///
+    /// Here we try to roughly match what rustc does to improve diagnostics: fn-like macros
+    /// get the whole `ast::MacroCall`, attribute macros get the attribute's range, and derives
+    /// get only the specific derive that is being referred to.
+    pub fn original_call_range(self, db: &dyn db::AstDatabase) -> FileRange {
+        let mut kind = self;
+        loop {
+            match kind.file_id().0 {
+                HirFileIdRepr::MacroFile(file) => {
+                    kind = db.lookup_intern_macro_call(file.macro_call_id).kind;
+                }
+                _ => break,
+            }
+        }
+
+        // `call_id` is now the outermost macro call, so its location is in a real file.
+        let file_id = match kind.file_id().0 {
+            HirFileIdRepr::FileId(it) => it,
+            HirFileIdRepr::MacroFile(_) => unreachable!("encountered unexpected macro file"),
+        };
+        let range = match kind {
+            MacroCallKind::FnLike { ast_id, .. } => ast_id.to_node(db).syntax().text_range(),
+            MacroCallKind::Derive { ast_id, derive_attr_index, .. } => {
+                // FIXME: should be the range of the macro name, not the whole derive
+                ast_id
+                    .to_node(db)
+                    .doc_comments_and_attrs()
+                    .nth(derive_attr_index as usize)
+                    .expect("missing derive")
+                    .expect_right("derive is a doc comment?")
+                    .syntax()
+                    .text_range()
+            }
+            MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => ast_id
+                .to_node(db)
+                .doc_comments_and_attrs()
+                .nth(invoc_attr_index as usize)
+                .expect("missing attribute")
+                .expect_right("attribute macro is a doc comment?")
+                .syntax()
+                .text_range(),
+        };
+
+        FileRange { range, file_id }
+    }
+
     fn arg(&self, db: &dyn db::AstDatabase) -> Option<SyntaxNode> {
         match self {
             MacroCallKind::FnLike { ast_id, .. } => {
@@ -623,15 +670,13 @@ impl<'a> InFile<&'a SyntaxNode> {
         }
 
         // Fall back to whole macro call.
-        let mut node = self.cloned();
-        while let Some(call_node) = node.file_id.call_node(db) {
-            node = call_node;
+        match self.file_id.0 {
+            HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value.text_range() },
+            HirFileIdRepr::MacroFile(mac_file) => {
+                let loc = db.lookup_intern_macro_call(mac_file.macro_call_id);
+                loc.kind.original_call_range(db)
+            }
         }
-
-        let orig_file = node.file_id.original_file(db);
-        assert_eq!(node.file_id, orig_file.into());
-
-        FileRange { file_id: orig_file, range: node.value.text_range() }
     }
 
     /// Attempts to map the syntax node back up its macro calls.
diff --git a/crates/proc_macro_srv/src/abis/abi_1_56/mod.rs b/crates/proc_macro_srv/src/abis/abi_1_56/mod.rs
index 96b71d3f21..ee1c2d4553 100644
--- a/crates/proc_macro_srv/src/abis/abi_1_56/mod.rs
+++ b/crates/proc_macro_srv/src/abis/abi_1_56/mod.rs
@@ -51,7 +51,7 @@ impl Abi {
                         &proc_macro::bridge::server::SameThread,
                         rustc_server::Rustc::default(),
                         parsed_body,
-                        false,
+                        true,
                     );
                     return res.map(|it| it.into_subtree()).map_err(PanicMessage::from);
                 }
@@ -62,7 +62,7 @@ impl Abi {
                         &proc_macro::bridge::server::SameThread,
                         rustc_server::Rustc::default(),
                         parsed_body,
-                        false,
+                        true,
                     );
                     return res.map(|it| it.into_subtree()).map_err(PanicMessage::from);
                 }
@@ -74,7 +74,7 @@ impl Abi {
                         rustc_server::Rustc::default(),
                         parsed_attributes,
                         parsed_body,
-                        false,
+                        true,
                     );
                     return res.map(|it| it.into_subtree()).map_err(PanicMessage::from);
                 }
diff --git a/crates/proc_macro_srv/src/abis/abi_1_58/mod.rs b/crates/proc_macro_srv/src/abis/abi_1_58/mod.rs
index 4421382006..dd3fd8b642 100644
--- a/crates/proc_macro_srv/src/abis/abi_1_58/mod.rs
+++ b/crates/proc_macro_srv/src/abis/abi_1_58/mod.rs
@@ -51,7 +51,7 @@ impl Abi {
                         &proc_macro::bridge::server::SameThread,
                         rustc_server::Rustc::default(),
                         parsed_body,
-                        false,
+                        true,
                     );
                     return res.map(|it| it.into_subtree()).map_err(PanicMessage::from);
                 }
@@ -62,7 +62,7 @@ impl Abi {
                         &proc_macro::bridge::server::SameThread,
                         rustc_server::Rustc::default(),
                         parsed_body,
-                        false,
+                        true,
                     );
                     return res.map(|it| it.into_subtree()).map_err(PanicMessage::from);
                 }
@@ -74,7 +74,7 @@ impl Abi {
                         rustc_server::Rustc::default(),
                         parsed_attributes,
                         parsed_body,
-                        false,
+                        true,
                     );
                     return res.map(|it| it.into_subtree()).map_err(PanicMessage::from);
                 }
diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs
index 8e55afb949..09c5af210f 100644
--- a/crates/syntax/src/ast/generated/nodes.rs
+++ b/crates/syntax/src/ast/generated/nodes.rs
@@ -162,6 +162,7 @@ pub struct MacroCall {
     pub(crate) syntax: SyntaxNode,
 }
 impl ast::HasAttrs for MacroCall {}
+impl ast::HasDocComments for MacroCall {}
 impl MacroCall {
     pub fn path(&self) -> Option<Path> { support::child(&self.syntax) }
     pub fn excl_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![!]) }
@@ -259,6 +260,7 @@ pub struct ExternBlock {
     pub(crate) syntax: SyntaxNode,
 }
 impl ast::HasAttrs for ExternBlock {}
+impl ast::HasDocComments for ExternBlock {}
 impl ExternBlock {
     pub fn abi(&self) -> Option<Abi> { support::child(&self.syntax) }
     pub fn extern_item_list(&self) -> Option<ExternItemList> { support::child(&self.syntax) }
@@ -270,6 +272,7 @@ pub struct ExternCrate {
 }
 impl ast::HasAttrs for ExternCrate {}
 impl ast::HasVisibility for ExternCrate {}
+impl ast::HasDocComments for ExternCrate {}
 impl ExternCrate {
     pub fn extern_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![extern]) }
     pub fn crate_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![crate]) }
@@ -1543,6 +1546,7 @@ pub enum Item {
     Use(Use),
 }
 impl ast::HasAttrs for Item {}
+impl ast::HasDocComments for Item {}
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub enum Stmt {
@@ -1597,6 +1601,7 @@ pub enum AssocItem {
     TypeAlias(TypeAlias),
 }
 impl ast::HasAttrs for AssocItem {}
+impl ast::HasDocComments for AssocItem {}
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub enum ExternItem {
@@ -1606,6 +1611,7 @@ pub enum ExternItem {
     TypeAlias(TypeAlias),
 }
 impl ast::HasAttrs for ExternItem {}
+impl ast::HasDocComments for ExternItem {}
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub enum GenericParam {
@@ -3902,10 +3908,9 @@ impl AnyHasDocComments {
 impl AstNode for AnyHasDocComments {
     fn can_cast(kind: SyntaxKind) -> bool {
         match kind {
-            SOURCE_FILE | CONST | ENUM | FN | IMPL | MACRO_RULES | MACRO_DEF | MODULE | STATIC
-            | STRUCT | TRAIT | TYPE_ALIAS | UNION | USE | RECORD_FIELD | TUPLE_FIELD | VARIANT => {
-                true
-            }
+            MACRO_CALL | SOURCE_FILE | CONST | ENUM | EXTERN_BLOCK | EXTERN_CRATE | FN | IMPL
+            | MACRO_RULES | MACRO_DEF | MODULE | STATIC | STRUCT | TRAIT | TYPE_ALIAS | UNION
+            | USE | RECORD_FIELD | TUPLE_FIELD | VARIANT => true,
             _ => false,
         }
     }
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index 470e9a04b3..705aa5edac 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -772,7 +772,6 @@ impl ast::HasLoopBody for ast::ForExpr {
 }
 
 impl ast::HasAttrs for ast::AnyHasDocComments {}
-impl ast::HasDocComments for ast::Item {}
 
 impl From<ast::Adt> for ast::Item {
     fn from(it: ast::Adt) -> Self {


Test patch:
diff --git a/crates/syntax/src/tests/sourcegen_ast.rs b/crates/syntax/src/tests/sourcegen_ast.rs
index ac928402f3..4a608da9ff 100644
--- a/crates/syntax/src/tests/sourcegen_ast.rs
+++ b/crates/syntax/src/tests/sourcegen_ast.rs
@@ -793,9 +793,11 @@ fn extract_struct_traits(ast: &mut AstSrc) {
         "Const",
         "TypeAlias",
         "Impl",
+        "ExternBlock",
+        "ExternCrate",
+        "MacroCall",
         "MacroRules",
         "MacroDef",
-        "Macro",
         "Use",
     ];