about summary refs log tree commit diff
diff options
context:
space:
mode:
authortamasfe <me@tamasfe.dev>2023-11-17 15:25:20 +0100
committerLukas Wirth <lukastw97@gmail.com>2024-02-12 12:50:40 +0100
commit6d45afd8d8a27b835045fe75035336f7d42fe195 (patch)
treef90364e333c0c4b86acf0df6a52d09f089c0d6be
parent5e1b09bb76cf8572b60939e4abb1951b0a39f67e (diff)
downloadrust-6d45afd8d8a27b835045fe75035336f7d42fe195.tar.gz
rust-6d45afd8d8a27b835045fe75035336f7d42fe195.zip
feat: ignored and disabled macro expansion
-rw-r--r--crates/base-db/src/input.rs1
-rw-r--r--crates/hir-def/src/data.rs11
-rw-r--r--crates/hir-def/src/nameres/collector.rs34
-rw-r--r--crates/hir-expand/src/lib.rs3
-rw-r--r--crates/hir-expand/src/proc_macro.rs25
-rw-r--r--crates/load-cargo/src/lib.rs14
-rw-r--r--crates/rust-analyzer/src/config.rs2
-rw-r--r--crates/rust-analyzer/src/reload.rs27
8 files changed, 105 insertions, 12 deletions
diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs
index 9560826e373..1db8c2f29d3 100644
--- a/crates/base-db/src/input.rs
+++ b/crates/base-db/src/input.rs
@@ -243,6 +243,7 @@ impl CrateDisplayName {
         CrateDisplayName { crate_name, canonical_name }
     }
 }
+
 pub type TargetLayoutLoadResult = Result<Arc<str>, Arc<str>>;
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
diff --git a/crates/hir-def/src/data.rs b/crates/hir-def/src/data.rs
index 7ce05b64d02..d6aab11afd5 100644
--- a/crates/hir-def/src/data.rs
+++ b/crates/hir-def/src/data.rs
@@ -634,7 +634,6 @@ impl<'a> AssocItemCollector<'a> {
                     attr,
                 ) {
                     Ok(ResolvedAttr::Macro(call_id)) => {
-                        self.attr_calls.push((ast_id, call_id));
                         // If proc attribute macro expansion is disabled, skip expanding it here
                         if !self.db.expand_proc_attr_macros() {
                             continue 'attrs;
@@ -647,10 +646,20 @@ impl<'a> AssocItemCollector<'a> {
                             // disabled. This is analogous to the handling in
                             // `DefCollector::collect_macros`.
                             if exp.is_dummy() {
+                                self.diagnostics.push(DefDiagnostic::unresolved_proc_macro(
+                                    self.module_id.local_id,
+                                    loc.kind,
+                                    loc.def.krate,
+                                ));
+
+                                continue 'attrs;
+                            } else if exp.is_disabled() {
                                 continue 'attrs;
                             }
                         }
 
+                        self.attr_calls.push((ast_id, call_id));
+
                         let res =
                             self.expander.enter_expand_id::<ast::MacroItems>(self.db, call_id);
                         self.collect_macro_items(res, &|| loc.kind.clone());
diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs
index 21cc28f1b3d..d3c8c813640 100644
--- a/crates/hir-def/src/nameres/collector.rs
+++ b/crates/hir-def/src/nameres/collector.rs
@@ -98,9 +98,13 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, def_map: DefMap, tree_id: TreeI
                         };
                         (
                             name.as_name(),
-                            CustomProcMacroExpander::new(hir_expand::proc_macro::ProcMacroId(
-                                idx as u32,
-                            )),
+                            if it.expander.should_expand() {
+                                CustomProcMacroExpander::new(hir_expand::proc_macro::ProcMacroId(
+                                    idx as u32,
+                                ))
+                            } else {
+                                CustomProcMacroExpander::disabled()
+                            },
                         )
                     })
                     .collect())
@@ -1156,6 +1160,28 @@ impl DefCollector<'_> {
                         self.def_map.modules[directive.module_id]
                             .scope
                             .add_macro_invoc(ast_id.ast_id, call_id);
+
+                        let loc: MacroCallLoc = self.db.lookup_intern_macro_call(call_id);
+
+                        if let MacroDefKind::ProcMacro(expander, _, _) = loc.def.kind {
+                            if expander.is_dummy() || expander.is_disabled() {
+                                // If there's no expander for the proc macro (e.g.
+                                // because proc macros are disabled, or building the
+                                // proc macro crate failed), report this and skip
+                                // expansion like we would if it was disabled
+                                self.def_map.diagnostics.push(
+                                    DefDiagnostic::unresolved_proc_macro(
+                                        directive.module_id,
+                                        loc.kind,
+                                        loc.def.krate,
+                                    ),
+                                );
+
+                                res = ReachedFixedPoint::No;
+                                return false;
+                            }
+                        }
+
                         push_resolved(directive, call_id);
 
                         res = ReachedFixedPoint::No;
@@ -1349,6 +1375,8 @@ impl DefCollector<'_> {
                             ));
 
                             return recollect_without(self);
+                        } else if exp.is_disabled() {
+                            return recollect_without(self);
                         }
                     }
 
diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs
index fd028182faf..42a8864c6a7 100644
--- a/crates/hir-expand/src/lib.rs
+++ b/crates/hir-expand/src/lib.rs
@@ -129,6 +129,8 @@ pub type ExpandResult<T> = ValueResult<T, ExpandError>;
 #[derive(Debug, PartialEq, Eq, Clone, Hash)]
 pub enum ExpandError {
     UnresolvedProcMacro(CrateId),
+    /// The macro expansion is disabled.
+    MacroDisabled,
     Mbe(mbe::ExpandError),
     RecursionOverflowPoisoned,
     Other(Box<Box<str>>),
@@ -160,6 +162,7 @@ impl fmt::Display for ExpandError {
                 f.write_str(it)
             }
             ExpandError::Other(it) => f.write_str(it),
+            ExpandError::MacroDisabled => f.write_str("macro disabled"),
         }
     }
 }
diff --git a/crates/hir-expand/src/proc_macro.rs b/crates/hir-expand/src/proc_macro.rs
index 70b47fc54b1..f745ff23efb 100644
--- a/crates/hir-expand/src/proc_macro.rs
+++ b/crates/hir-expand/src/proc_macro.rs
@@ -31,6 +31,16 @@ pub trait ProcMacroExpander: fmt::Debug + Send + Sync + RefUnwindSafe {
         call_site: Span,
         mixed_site: Span,
     ) -> Result<tt::Subtree, ProcMacroExpansionError>;
+
+    /// If this returns `false`, expansions via [`expand`](ProcMacroExpander::expand) will always
+    /// return the input subtree or will always return an error.
+    ///
+    /// This is used to skip any additional expansion-related work,
+    /// e.g. to make sure we do not touch the syntax tree in any way
+    /// if a proc macro will never be expanded.
+    fn should_expand(&self) -> bool {
+        true
+    }
 }
 
 #[derive(Debug)]
@@ -57,6 +67,7 @@ pub struct CustomProcMacroExpander {
 }
 
 const DUMMY_ID: u32 = !0;
+const DISABLED_ID: u32 = !1;
 
 impl CustomProcMacroExpander {
     pub fn new(proc_macro_id: ProcMacroId) -> Self {
@@ -68,10 +79,20 @@ impl CustomProcMacroExpander {
         Self { proc_macro_id: ProcMacroId(DUMMY_ID) }
     }
 
+    /// The macro was not yet resolved.
     pub fn is_dummy(&self) -> bool {
         self.proc_macro_id.0 == DUMMY_ID
     }
 
+    pub fn disabled() -> Self {
+        Self { proc_macro_id: ProcMacroId(DISABLED_ID) }
+    }
+
+    /// The macro is explicitly disabled and cannot be expanded.
+    pub fn is_disabled(&self) -> bool {
+        self.proc_macro_id.0 == DISABLED_ID
+    }
+
     pub fn expand(
         self,
         db: &dyn ExpandDatabase,
@@ -88,6 +109,10 @@ impl CustomProcMacroExpander {
                 tt::Subtree::empty(tt::DelimSpan { open: call_site, close: call_site }),
                 ExpandError::UnresolvedProcMacro(def_crate),
             ),
+            ProcMacroId(DISABLED_ID) => ExpandResult::new(
+                tt::Subtree::empty(tt::DelimSpan { open: call_site, close: call_site }),
+                ExpandError::MacroDisabled,
+            ),
             ProcMacroId(id) => {
                 let proc_macros = db.proc_macros();
                 let proc_macros = match proc_macros.get(&def_crate) {
diff --git a/crates/load-cargo/src/lib.rs b/crates/load-cargo/src/lib.rs
index c6dc071c394..dc9005f5838 100644
--- a/crates/load-cargo/src/lib.rs
+++ b/crates/load-cargo/src/lib.rs
@@ -273,7 +273,7 @@ impl SourceRootConfig {
 pub fn load_proc_macro(
     server: &ProcMacroServer,
     path: &AbsPath,
-    dummy_replace: &[Box<str>],
+    ignored_macros: &[Box<str>],
 ) -> ProcMacroLoadResult {
     let res: Result<Vec<_>, String> = (|| {
         let dylib = MacroDylib::new(path.to_path_buf());
@@ -283,7 +283,7 @@ pub fn load_proc_macro(
         }
         Ok(vec
             .into_iter()
-            .map(|expander| expander_to_proc_macro(expander, dummy_replace))
+            .map(|expander| expander_to_proc_macro(expander, ignored_macros))
             .collect())
     })();
     match res {
@@ -349,7 +349,7 @@ fn load_crate_graph(
 
 fn expander_to_proc_macro(
     expander: proc_macro_api::ProcMacro,
-    dummy_replace: &[Box<str>],
+    ignored_macros: &[Box<str>],
 ) -> ProcMacro {
     let name = From::from(expander.name());
     let kind = match expander.kind() {
@@ -358,7 +358,7 @@ fn expander_to_proc_macro(
         proc_macro_api::ProcMacroKind::Attr => ProcMacroKind::Attr,
     };
     let expander: sync::Arc<dyn ProcMacroExpander> =
-        if dummy_replace.iter().any(|replace| **replace == name) {
+        if ignored_macros.iter().any(|replace| &**replace == name) {
             match kind {
                 ProcMacroKind::Attr => sync::Arc::new(IdentityExpander),
                 _ => sync::Arc::new(EmptyExpander),
@@ -407,6 +407,9 @@ impl ProcMacroExpander for IdentityExpander {
     ) -> Result<tt::Subtree<Span>, ProcMacroExpansionError> {
         Ok(subtree.clone())
     }
+    fn should_expand(&self) -> bool {
+        false
+    }
 }
 
 /// Empty expander, used for proc-macros that are deliberately ignored by the user.
@@ -425,6 +428,9 @@ impl ProcMacroExpander for EmptyExpander {
     ) -> Result<tt::Subtree<Span>, ProcMacroExpansionError> {
         Ok(tt::Subtree::empty(DelimSpan { open: call_site, close: call_site }))
     }
+    fn should_expand(&self) -> bool {
+        false
+    }
 }
 
 #[cfg(test)]
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 7bdd9ec866a..bf3e71a6bdb 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -1202,7 +1202,7 @@ impl Config {
         Some(AbsPathBuf::try_from(path).unwrap_or_else(|path| self.root_path.join(path)))
     }
 
-    pub fn dummy_replacements(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
+    pub fn ignored_proc_macros(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
         &self.data.procMacro_ignored
     }
 
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 5a5d26e0b05..14d622abfe2 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -299,13 +299,13 @@ impl GlobalState {
 
     pub(crate) fn fetch_proc_macros(&mut self, cause: Cause, paths: Vec<ProcMacroPaths>) {
         tracing::info!(%cause, "will load proc macros");
-        let dummy_replacements = self.config.dummy_replacements().clone();
+        let ignored_proc_macros = self.config.ignored_proc_macros().clone();
         let proc_macro_clients = self.proc_macro_clients.clone();
 
         self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, move |sender| {
             sender.send(Task::LoadProcMacros(ProcMacroProgress::Begin)).unwrap();
 
-            let dummy_replacements = &dummy_replacements;
+            let ignored_proc_macros = &ignored_proc_macros;
             let progress = {
                 let sender = sender.clone();
                 &move |msg| {
@@ -333,7 +333,13 @@ impl GlobalState {
                                         crate_name
                                             .as_deref()
                                             .and_then(|crate_name| {
-                                                dummy_replacements.get(crate_name).map(|v| &**v)
+                                                ignored_proc_macros.iter().find_map(|c| {
+                                                    if eq_ignore_underscore(&*c.0, crate_name) {
+                                                        Some(&**c.1)
+                                                    } else {
+                                                        None
+                                                    }
+                                                })
                                             })
                                             .unwrap_or_default(),
                                     )
@@ -695,3 +701,18 @@ pub(crate) fn should_refresh_for_change(path: &AbsPath, change_kind: ChangeKind)
     }
     false
 }
+
+/// Similar to [`str::eq_ignore_ascii_case`] but instead of ignoring
+/// case, we say that `-` and `_` are equal.
+fn eq_ignore_underscore(s1: &str, s2: &str) -> bool {
+    if s1.len() != s2.len() {
+        return false;
+    }
+
+    s1.as_bytes().iter().zip(s2.as_bytes()).all(|(c1, c2)| {
+        let c1_underscore = c1 == &b'_' || c1 == &b'-';
+        let c2_underscore = c2 == &b'_' || c2 == &b'-';
+
+        c1 == c2 || (c1_underscore && c2_underscore)
+    })
+}