Repository: rust-analyzer/rust-analyzer
Pull request #9060 (2021-05-30 02:20:42)

feat: Diagnose unimplemented built-in macros
A number of built-in attribute macros are unsupported, I thought it might be useful to put a diagnostic on their definition in libcore. Not sure.


Patch:
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 22ec7c6aca..2cdbd172a5 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -227,3 +227,27 @@ impl Diagnostic for MacroError {
         true
     }
 }
+
+#[derive(Debug)]
+pub struct UnimplementedBuiltinMacro {
+    pub file: HirFileId,
+    pub node: SyntaxNodePtr,
+}
+
+impl Diagnostic for UnimplementedBuiltinMacro {
+    fn code(&self) -> DiagnosticCode {
+        DiagnosticCode("unimplemented-builtin-macro")
+    }
+
+    fn message(&self) -> String {
+        "unimplemented built-in macro".to_string()
+    }
+
+    fn display_source(&self) -> InFile<SyntaxNodePtr> {
+        InFile::new(self.file, self.node.clone())
+    }
+
+    fn as_any(&self) -> &(dyn Any + Send + 'static) {
+        self
+    }
+}
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 975ae48690..d3ef29db4e 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -36,8 +36,8 @@ use std::{iter, sync::Arc};
 use arrayvec::ArrayVec;
 use base_db::{CrateDisplayName, CrateId, Edition, FileId};
 use diagnostics::{
-    InactiveCode, MacroError, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall,
-    UnresolvedModule, UnresolvedProcMacro,
+    InactiveCode, MacroError, UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport,
+    UnresolvedMacroCall, UnresolvedModule, UnresolvedProcMacro,
 };
 use either::Either;
 use hir_def::{
@@ -565,6 +565,14 @@ impl Module {
                     };
                     sink.push(MacroError { file, node: ast, message: message.clone() });
                 }
+
+                DefDiagnosticKind::UnimplementedBuiltinMacro { ast } => {
+                    let node = ast.to_node(db.upcast());
+                    // Must have a name, otherwise we wouldn't emit it.
+                    let name = node.name().expect("unimplemented builtin macro with no name");
+                    let ptr = SyntaxNodePtr::from(AstPtr::new(&name));
+                    sink.push(UnimplementedBuiltinMacro { file: ast.file_id, node: ptr });
+                }
             }
         }
         for decl in self.declarations(db) {
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index d9d6c91a85..50b2b0af4e 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -1679,14 +1679,22 @@ impl ModCollector<'_, '_> {
                 None => &mac.name,
             };
             let krate = self.def_collector.def_map.krate;
-            if let Some(macro_id) = find_builtin_macro(name, krate, ast_id) {
-                self.def_collector.define_macro_rules(
-                    self.module_id,
-                    mac.name.clone(),
-                    macro_id,
-                    is_export,
-                );
-                return;
+            match find_builtin_macro(name, krate, ast_id) {
+                Some(macro_id) => {
+                    self.def_collector.define_macro_rules(
+                        self.module_id,
+                        mac.name.clone(),
+                        macro_id,
+                        is_export,
+                    );
+                    return;
+                }
+                None => {
+                    self.def_collector
+                        .def_map
+                        .diagnostics
+                        .push(DefDiagnostic::unimplemented_builtin_macro(self.module_id, ast_id));
+                }
             }
         }
 
@@ -1715,15 +1723,23 @@ impl ModCollector<'_, '_> {
             let macro_id = find_builtin_macro(&mac.name, krate, ast_id)
                 .or_else(|| find_builtin_derive(&mac.name, krate, ast_id));
 
-            if let Some(macro_id) = macro_id {
-                self.def_collector.define_macro_def(
-                    self.module_id,
-                    mac.name.clone(),
-                    macro_id,
-                    &self.item_tree[mac.visibility],
-                );
+            match macro_id {
+                Some(macro_id) => {
+                    self.def_collector.define_macro_def(
+                        self.module_id,
+                        mac.name.clone(),
+                        macro_id,
+                        &self.item_tree[mac.visibility],
+                    );
+                    return;
+                }
+                None => {
+                    self.def_collector
+                        .def_map
+                        .diagnostics
+                        .push(DefDiagnostic::unimplemented_builtin_macro(self.module_id, ast_id));
+                }
             }
-            return;
         }
 
         // Case 2: normal `macro`
diff --git a/crates/hir_def/src/nameres/diagnostics.rs b/crates/hir_def/src/nameres/diagnostics.rs
index 57c36c3c6c..95061f6015 100644
--- a/crates/hir_def/src/nameres/diagnostics.rs
+++ b/crates/hir_def/src/nameres/diagnostics.rs
@@ -27,6 +27,8 @@ pub enum DefDiagnosticKind {
     UnresolvedMacroCall { ast: AstId<ast::MacroCall>, path: ModPath },
 
     MacroError { ast: MacroCallKind, message: String },
+
+    UnimplementedBuiltinMacro { ast: AstId<ast::Macro> },
 }
 
 #[derive(Debug, PartialEq, Eq)]
@@ -93,4 +95,11 @@ impl DefDiagnostic {
     ) -> Self {
         Self { in_module: container, kind: DefDiagnosticKind::UnresolvedMacroCall { ast, path } }
     }
+
+    pub(super) fn unimplemented_builtin_macro(
+        container: LocalModuleId,
+        ast: AstId<ast::Macro>,
+    ) -> Self {
+        Self { in_module: container, kind: DefDiagnosticKind::UnimplementedBuiltinMacro { ast } }
+    }
 }
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 6cf5810fa4..d5c954b8bb 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -182,6 +182,11 @@ pub(crate) fn diagnostics(
             res.borrow_mut()
                 .push(Diagnostic::error(display_range, d.message()).with_code(Some(d.code())));
         })
+        .on::<hir::diagnostics::UnimplementedBuiltinMacro, _>(|d| {
+            let display_range = sema.diagnostics_display_range(d.display_source()).range;
+            res.borrow_mut()
+                .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code())));
+        })
         // Only collect experimental diagnostics when they're enabled.
         .filter(|diag| !(diag.is_experimental() && config.disable_experimental))
         .filter(|diag| !config.disabled.contains(diag.code().as_str()));


Test patch:
diff --git a/crates/hir_def/src/test_db.rs b/crates/hir_def/src/test_db.rs
index a9c1e13e2f..e840fe5e80 100644
--- a/crates/hir_def/src/test_db.rs
+++ b/crates/hir_def/src/test_db.rs
@@ -298,6 +298,13 @@ impl TestDB {
                     DefDiagnosticKind::MacroError { ast, message } => {
                         (ast.to_node(self.upcast()), message.as_str())
                     }
+                    DefDiagnosticKind::UnimplementedBuiltinMacro { ast } => {
+                        let node = ast.to_node(self.upcast());
+                        (
+                            InFile::new(ast.file_id, node.syntax().clone()),
+                            "UnimplementedBuiltinMacro",
+                        )
+                    }
                 };
 
                 let frange = node.as_ref().original_file_range(self);