Skip to content

Commit 866599b

Browse files
committed
feat: ignored and disabled macro expansion
1 parent 246d11b commit 866599b

File tree

7 files changed

+119
-16
lines changed

7 files changed

+119
-16
lines changed

crates/base-db/src/input.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,16 @@ pub trait ProcMacroExpander: fmt::Debug + Send + Sync + RefUnwindSafe {
255255
attrs: Option<&Subtree>,
256256
env: &Env,
257257
) -> Result<Subtree, ProcMacroExpansionError>;
258+
259+
/// If this returns `false`, expansions via [`expand`](ProcMacroExpander::expand) will always
260+
/// return the input subtree or will always return an error.
261+
///
262+
/// This is used to skip any additional expansion-related work,
263+
/// e.g. to make sure we do not touch the syntax tree in any way
264+
/// if a proc macro will never be expanded.
265+
fn should_expand(&self) -> bool {
266+
true
267+
}
258268
}
259269

260270
pub enum ProcMacroExpansionError {

crates/hir-def/src/data.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,6 @@ impl<'a> AssocItemCollector<'a> {
618618
attr,
619619
) {
620620
Ok(ResolvedAttr::Macro(call_id)) => {
621-
self.attr_calls.push((ast_id, call_id));
622621
// If proc attribute macro expansion is disabled, skip expanding it here
623622
if !self.db.expand_proc_attr_macros() {
624623
continue 'attrs;
@@ -631,10 +630,20 @@ impl<'a> AssocItemCollector<'a> {
631630
// disabled. This is analogous to the handling in
632631
// `DefCollector::collect_macros`.
633632
if exp.is_dummy() {
633+
self.diagnostics.push(DefDiagnostic::unresolved_proc_macro(
634+
self.module_id.local_id,
635+
loc.kind,
636+
loc.def.krate,
637+
));
638+
639+
continue 'attrs;
640+
} else if exp.is_disabled() {
634641
continue 'attrs;
635642
}
636643
}
637644

645+
self.attr_calls.push((ast_id, call_id));
646+
638647
let res =
639648
self.expander.enter_expand_id::<ast::MacroItems>(self.db, call_id);
640649
self.collect_macro_items(res, &|| loc.kind.clone());

crates/hir-def/src/nameres/collector.rs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,15 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, def_map: DefMap, tree_id: TreeI
8787
// FIXME: a hacky way to create a Name from string.
8888
let name =
8989
tt::Ident { text: it.name.clone(), span: tt::TokenId::unspecified() };
90-
(name.as_name(), ProcMacroExpander::new(base_db::ProcMacroId(idx as u32)))
90+
91+
(
92+
name.as_name(),
93+
if it.expander.should_expand() {
94+
ProcMacroExpander::new(base_db::ProcMacroId(idx as u32))
95+
} else {
96+
ProcMacroExpander::disabled()
97+
},
98+
)
9199
})
92100
.collect())
93101
}
@@ -1122,6 +1130,27 @@ impl DefCollector<'_> {
11221130
resolver_def_id,
11231131
);
11241132
if let Ok(Some(call_id)) = call_id {
1133+
let loc: MacroCallLoc = self.db.lookup_intern_macro_call(call_id);
1134+
1135+
if let MacroDefKind::ProcMacro(expander, _, _) = loc.def.kind {
1136+
if expander.is_dummy() || expander.is_disabled() {
1137+
// If there's no expander for the proc macro (e.g.
1138+
// because proc macros are disabled, or building the
1139+
// proc macro crate failed), report this and skip
1140+
// expansion like we would if it was disabled
1141+
self.def_map.diagnostics.push(
1142+
DefDiagnostic::unresolved_proc_macro(
1143+
directive.module_id,
1144+
loc.kind,
1145+
loc.def.krate,
1146+
),
1147+
);
1148+
1149+
res = ReachedFixedPoint::No;
1150+
return false;
1151+
}
1152+
}
1153+
11251154
push_resolved(directive, call_id);
11261155

11271156
res = ReachedFixedPoint::No;
@@ -1322,6 +1351,8 @@ impl DefCollector<'_> {
13221351
loc.def.krate,
13231352
));
13241353

1354+
return recollect_without(self);
1355+
} else if exp.is_disabled() {
13251356
return recollect_without(self);
13261357
}
13271358
}
@@ -2238,8 +2269,12 @@ impl ModCollector<'_, '_> {
22382269
}
22392270

22402271
fn import_all_legacy_macros(&mut self, module_id: LocalModuleId) {
2241-
let Some((source, target)) = Self::borrow_modules(self.def_collector.def_map.modules.as_mut(), module_id, self.module_id) else {
2242-
return
2272+
let Some((source, target)) = Self::borrow_modules(
2273+
self.def_collector.def_map.modules.as_mut(),
2274+
module_id,
2275+
self.module_id,
2276+
) else {
2277+
return;
22432278
};
22442279

22452280
for (name, macs) in source.scope.legacy_macros() {

crates/hir-expand/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ pub type ExpandResult<T> = ValueResult<T, ExpandError>;
5656
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
5757
pub enum ExpandError {
5858
UnresolvedProcMacro(CrateId),
59+
/// The macro expansion is disabled.
60+
MacroDisabled,
5961
Mbe(mbe::ExpandError),
6062
RecursionOverflowPoisoned,
6163
Other(Box<Box<str>>),
@@ -82,6 +84,7 @@ impl fmt::Display for ExpandError {
8284
f.write_str("overflow expanding the original macro")
8385
}
8486
ExpandError::Other(it) => f.write_str(it),
87+
ExpandError::MacroDisabled => f.write_str("macro disabled"),
8588
}
8689
}
8790
}

crates/hir-expand/src/proc_macro.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub struct ProcMacroExpander {
1111
}
1212

1313
const DUMMY_ID: u32 = !0;
14+
const DISABLED_ID: u32 = !1;
1415

1516
impl ProcMacroExpander {
1617
pub fn new(proc_macro_id: ProcMacroId) -> Self {
@@ -22,10 +23,20 @@ impl ProcMacroExpander {
2223
Self { proc_macro_id: ProcMacroId(DUMMY_ID) }
2324
}
2425

26+
/// The macro was not yet resolved.
2527
pub fn is_dummy(&self) -> bool {
2628
self.proc_macro_id.0 == DUMMY_ID
2729
}
2830

31+
pub fn disabled() -> Self {
32+
Self { proc_macro_id: ProcMacroId(DISABLED_ID) }
33+
}
34+
35+
/// The macro is explicitly disabled and cannot be expanded.
36+
pub fn is_disabled(&self) -> bool {
37+
self.proc_macro_id.0 == DISABLED_ID
38+
}
39+
2940
pub fn expand(
3041
self,
3142
db: &dyn ExpandDatabase,
@@ -38,6 +49,9 @@ impl ProcMacroExpander {
3849
ProcMacroId(DUMMY_ID) => {
3950
ExpandResult::new(tt::Subtree::empty(), ExpandError::UnresolvedProcMacro(def_crate))
4051
}
52+
ProcMacroId(DISABLED_ID) => {
53+
ExpandResult::new(tt::Subtree::empty(), ExpandError::MacroDisabled)
54+
}
4155
ProcMacroId(id) => {
4256
let proc_macros = db.proc_macros();
4357
let proc_macros = match proc_macros.get(&def_crate) {

crates/rust-analyzer/src/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1133,7 +1133,7 @@ impl Config {
11331133
Some(AbsPathBuf::try_from(path).unwrap_or_else(|path| self.root_path.join(&path)))
11341134
}
11351135

1136-
pub fn dummy_replacements(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
1136+
pub fn ignored_proc_macros(&self) -> &FxHashMap<Box<str>, Box<[Box<str>]>> {
11371137
&self.data.procMacro_ignored
11381138
}
11391139

crates/rust-analyzer/src/reload.rs

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -281,13 +281,13 @@ impl GlobalState {
281281

282282
pub(crate) fn fetch_proc_macros(&mut self, cause: Cause, paths: Vec<ProcMacroPaths>) {
283283
tracing::info!(%cause, "will load proc macros");
284-
let dummy_replacements = self.config.dummy_replacements().clone();
284+
let ignored_proc_macros = self.config.ignored_proc_macros().clone();
285285
let proc_macro_clients = self.proc_macro_clients.clone();
286286

287287
self.task_pool.handle.spawn_with_sender(ThreadIntent::Worker, move |sender| {
288288
sender.send(Task::LoadProcMacros(ProcMacroProgress::Begin)).unwrap();
289289

290-
let dummy_replacements = &dummy_replacements;
290+
let ignored_proc_macros = &ignored_proc_macros;
291291
let progress = {
292292
let sender = sender.clone();
293293
&move |msg| {
@@ -315,7 +315,13 @@ impl GlobalState {
315315
crate_name
316316
.as_deref()
317317
.and_then(|crate_name| {
318-
dummy_replacements.get(crate_name).map(|v| &**v)
318+
ignored_proc_macros.iter().find_map(|c| {
319+
if eq_ignore_underscore(&*c.0, crate_name) {
320+
Some(&**c.1)
321+
} else {
322+
None
323+
}
324+
})
319325
})
320326
.unwrap_or_default(),
321327
)
@@ -340,7 +346,11 @@ impl GlobalState {
340346
let _p = profile::span("GlobalState::switch_workspaces");
341347
tracing::info!(%cause, "will switch workspaces");
342348

343-
let Some((workspaces, force_reload_crate_graph)) = self.fetch_workspaces_queue.last_op_result() else { return; };
349+
let Some((workspaces, force_reload_crate_graph)) =
350+
self.fetch_workspaces_queue.last_op_result()
351+
else {
352+
return;
353+
};
344354

345355
if let Err(_) = self.fetch_workspace_error() {
346356
if !self.workspaces.is_empty() {
@@ -531,7 +541,7 @@ impl GlobalState {
531541
let mut buf = String::new();
532542

533543
let Some((last_op_result, _)) = self.fetch_workspaces_queue.last_op_result() else {
534-
return Ok(())
544+
return Ok(());
535545
};
536546
if last_op_result.is_empty() {
537547
stdx::format_to!(buf, "rust-analyzer failed to discover workspace");
@@ -762,12 +772,11 @@ impl SourceRootConfig {
762772
}
763773
}
764774

765-
/// Load the proc-macros for the given lib path, replacing all expanders whose names are in `dummy_replace`
766-
/// with an identity dummy expander.
775+
/// Load the proc-macros for the given lib path, marking them as ignored if needed.
767776
pub(crate) fn load_proc_macro(
768777
server: &ProcMacroServer,
769778
path: &AbsPath,
770-
dummy_replace: &[Box<str>],
779+
ignored_macros: &[Box<str>],
771780
) -> ProcMacroLoadResult {
772781
let res: Result<Vec<_>, String> = (|| {
773782
let dylib = MacroDylib::new(path.to_path_buf());
@@ -777,7 +786,7 @@ pub(crate) fn load_proc_macro(
777786
}
778787
Ok(vec
779788
.into_iter()
780-
.map(|expander| expander_to_proc_macro(expander, dummy_replace))
789+
.map(|expander| expander_to_proc_macro(expander, ignored_macros))
781790
.collect())
782791
})();
783792
return match res {
@@ -796,7 +805,7 @@ pub(crate) fn load_proc_macro(
796805

797806
fn expander_to_proc_macro(
798807
expander: proc_macro_api::ProcMacro,
799-
dummy_replace: &[Box<str>],
808+
ignored_macros: &[Box<str>],
800809
) -> ProcMacro {
801810
let name = SmolStr::from(expander.name());
802811
let kind = match expander.kind() {
@@ -805,7 +814,7 @@ pub(crate) fn load_proc_macro(
805814
proc_macro_api::ProcMacroKind::Attr => ProcMacroKind::Attr,
806815
};
807816
let expander: sync::Arc<dyn ProcMacroExpander> =
808-
if dummy_replace.iter().any(|replace| &**replace == name) {
817+
if ignored_macros.iter().any(|replace| &**replace == name) {
809818
match kind {
810819
ProcMacroKind::Attr => sync::Arc::new(IdentityExpander),
811820
_ => sync::Arc::new(EmptyExpander),
@@ -848,6 +857,10 @@ pub(crate) fn load_proc_macro(
848857
) -> Result<tt::Subtree, ProcMacroExpansionError> {
849858
Ok(subtree.clone())
850859
}
860+
861+
fn should_expand(&self) -> bool {
862+
false
863+
}
851864
}
852865

853866
/// Empty expander, used for proc-macros that are deliberately ignored by the user.
@@ -863,6 +876,10 @@ pub(crate) fn load_proc_macro(
863876
) -> Result<tt::Subtree, ProcMacroExpansionError> {
864877
Ok(tt::Subtree::empty())
865878
}
879+
880+
fn should_expand(&self) -> bool {
881+
false
882+
}
866883
}
867884
}
868885

@@ -910,3 +927,18 @@ pub(crate) fn should_refresh_for_change(path: &AbsPath, change_kind: ChangeKind)
910927
}
911928
false
912929
}
930+
931+
/// Similar to [`str::eq_ignore_ascii_case`] but instead of ignoring
932+
/// case, we say that `-` and `_` are equal.
933+
fn eq_ignore_underscore(s1: &str, s2: &str) -> bool {
934+
if s1.len() != s2.len() {
935+
return false;
936+
}
937+
938+
s1.as_bytes().iter().zip(s2.as_bytes()).all(|(c1, c2)| {
939+
let c1_underscore = c1 == &b'_' || c1 == &b'-';
940+
let c2_underscore = c2 == &b'_' || c2 == &b'-';
941+
942+
c1 == c2 || (c1_underscore && c2_underscore)
943+
})
944+
}

0 commit comments

Comments
 (0)