diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs index 9560826e37..1db8c2f29d 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>; #[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 7ce05b64d0..d6aab11afd 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::(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 21cc28f1b3..d3c8c81364 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; @@ -1348,6 +1374,8 @@ impl DefCollector<'_> { def.krate, )); + 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 fd028182fa..42a8864c6a 100644 --- a/crates/hir-expand/src/lib.rs +++ b/crates/hir-expand/src/lib.rs @@ -129,6 +129,8 @@ pub type ExpandResult = ValueResult; #[derive(Debug, PartialEq, Eq, Clone, Hash)] pub enum ExpandError { UnresolvedProcMacro(CrateId), + /// The macro expansion is disabled. + MacroDisabled, Mbe(mbe::ExpandError), RecursionOverflowPoisoned, Other(Box>), @@ -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 70b47fc54b..f745ff23ef 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; + + /// 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 c6dc071c39..dc9005f583 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], + ignored_macros: &[Box], ) -> ProcMacroLoadResult { let res: Result, 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], + ignored_macros: &[Box], ) -> 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 = - 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, 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, 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 7bdd9ec866..bf3e71a6bd 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<[Box]>> { + pub fn ignored_proc_macros(&self) -> &FxHashMap, Box<[Box]>> { &self.data.procMacro_ignored } diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index 5a5d26e0b0..14d622abfe 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) { 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) + }) +}