Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #13730 - lowr:feat/builtin-macro-helper-attr, r=Veykril
Support builtin derive macro helper attributes Closes #13244 It's a bit wasteful for `Macro2Data` to have `helpers` field currently just for `Default` derive macro, but I tend to think it's okay for the time being given how rare macro2's are used.
bors 2022-12-06
parent f9bd487 · parent 051c659 · commit df07c8f
-rw-r--r--crates/hir-def/src/data.rs17
-rw-r--r--crates/hir-def/src/nameres/collector.rs28
-rw-r--r--crates/hir-def/src/nameres/proc_macro.rs78
-rw-r--r--crates/hir-def/src/nameres/tests/macros.rs22
-rw-r--r--crates/hir/src/lib.rs6
-rw-r--r--crates/test-utils/src/minicore.rs2
6 files changed, 113 insertions, 40 deletions
diff --git a/crates/hir-def/src/data.rs b/crates/hir-def/src/data.rs
index 0e7acda4a7..b78ab71ef2 100644
--- a/crates/hir-def/src/data.rs
+++ b/crates/hir-def/src/data.rs
@@ -13,7 +13,9 @@ use crate::{
intern::Interned,
item_tree::{self, AssocItem, FnFlags, ItemTree, ItemTreeId, ModItem, Param, TreeId},
nameres::{
- attr_resolution::ResolvedAttr, diagnostics::DefDiagnostic, proc_macro::ProcMacroKind,
+ attr_resolution::ResolvedAttr,
+ diagnostics::DefDiagnostic,
+ proc_macro::{parse_macro_name_and_helper_attrs, ProcMacroKind},
DefMap,
},
type_ref::{TraitRef, TypeBound, TypeRef},
@@ -348,6 +350,10 @@ impl ImplData {
pub struct Macro2Data {
pub name: Name,
pub visibility: RawVisibility,
+ // It's a bit wasteful as currently this is only for builtin `Default` derive macro, but macro2
+ // are rarely used in practice so I think it's okay for now.
+ /// Derive helpers, if this is a derive rustc_builtin_macro
+ pub helpers: Option<Box<[Name]>>,
}
impl Macro2Data {
@@ -356,9 +362,18 @@ impl Macro2Data {
let item_tree = loc.id.item_tree(db);
let makro = &item_tree[loc.id.value];
+ let helpers = item_tree
+ .attrs(db, loc.container.krate(), ModItem::from(loc.id.value).into())
+ .by_key("rustc_builtin_macro")
+ .tt_values()
+ .next()
+ .and_then(|attr| parse_macro_name_and_helper_attrs(&attr.token_trees))
+ .map(|(_, helpers)| helpers);
+
Arc::new(Macro2Data {
name: makro.name.clone(),
visibility: item_tree[makro.visibility].clone(),
+ helpers,
})
}
}
diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs
index b0dd01f9db..f21d674f20 100644
--- a/crates/hir-def/src/nameres/collector.rs
+++ b/crates/hir-def/src/nameres/collector.rs
@@ -40,7 +40,7 @@ use crate::{
diagnostics::DefDiagnostic,
mod_resolution::ModDir,
path_resolution::ReachedFixedPoint,
- proc_macro::{ProcMacroDef, ProcMacroKind},
+ proc_macro::{parse_macro_name_and_helper_attrs, ProcMacroDef, ProcMacroKind},
BuiltinShadowMode, DefMap, ModuleData, ModuleOrigin, ResolveMode,
},
path::{ImportAlias, ModPath, PathKind},
@@ -2005,6 +2005,7 @@ impl ModCollector<'_, '_> {
let ast_id = InFile::new(self.file_id(), mac.ast_id.upcast());
// Case 1: builtin macros
+ let mut helpers_opt = None;
let attrs = self.item_tree.attrs(self.def_collector.db, krate, ModItem::from(id).into());
let expander = if attrs.by_key("rustc_builtin_macro").exists() {
if let Some(expander) = find_builtin_macro(&mac.name) {
@@ -2013,6 +2014,25 @@ impl ModCollector<'_, '_> {
Either::Right(it) => MacroExpander::BuiltInEager(it),
}
} else if let Some(expander) = find_builtin_derive(&mac.name) {
+ if let Some(attr) = attrs.by_key("rustc_builtin_macro").tt_values().next() {
+ // NOTE: The item *may* have both `#[rustc_builtin_macro]` and `#[proc_macro_derive]`,
+ // in which case rustc ignores the helper attributes from the latter, but it
+ // "doesn't make sense in practice" (see rust-lang/rust#87027).
+ if let Some((name, helpers)) =
+ parse_macro_name_and_helper_attrs(&attr.token_trees)
+ {
+ // NOTE: rustc overrides the name if the macro name if it's different from the
+ // macro name, but we assume it isn't as there's no such case yet. FIXME if
+ // the following assertion fails.
+ stdx::always!(
+ name == mac.name,
+ "built-in macro {} has #[rustc_builtin_macro] which declares different name {}",
+ mac.name,
+ name
+ );
+ helpers_opt = Some(helpers);
+ }
+ }
MacroExpander::BuiltInDerive(expander)
} else if let Some(expander) = find_builtin_attr(&mac.name) {
MacroExpander::BuiltInAttr(expander)
@@ -2037,6 +2057,12 @@ impl ModCollector<'_, '_> {
macro_id,
&self.item_tree[mac.visibility],
);
+ if let Some(helpers) = helpers_opt {
+ self.def_collector
+ .def_map
+ .exported_derives
+ .insert(macro_id_to_def_id(self.def_collector.db, macro_id.into()), helpers);
+ }
}
fn collect_macro_call(&mut self, mac: &MacroCall, container: ItemContainerId) {
diff --git a/crates/hir-def/src/nameres/proc_macro.rs b/crates/hir-def/src/nameres/proc_macro.rs
index 52b79cd0fd..06b23392cf 100644
--- a/crates/hir-def/src/nameres/proc_macro.rs
+++ b/crates/hir-def/src/nameres/proc_macro.rs
@@ -37,45 +37,53 @@ impl Attrs {
Some(ProcMacroDef { name: func_name.clone(), kind: ProcMacroKind::Attr })
} else if self.by_key("proc_macro_derive").exists() {
let derive = self.by_key("proc_macro_derive").tt_values().next()?;
+ let def = parse_macro_name_and_helper_attrs(&derive.token_trees)
+ .map(|(name, helpers)| ProcMacroDef { name, kind: ProcMacroKind::CustomDerive { helpers } });
- match &*derive.token_trees {
- // `#[proc_macro_derive(Trait)]`
- [TokenTree::Leaf(Leaf::Ident(trait_name))] => Some(ProcMacroDef {
- name: trait_name.as_name(),
- kind: ProcMacroKind::CustomDerive { helpers: Box::new([]) },
- }),
-
- // `#[proc_macro_derive(Trait, attributes(helper1, helper2, ...))]`
- [
- TokenTree::Leaf(Leaf::Ident(trait_name)),
- TokenTree::Leaf(Leaf::Punct(comma)),
- TokenTree::Leaf(Leaf::Ident(attributes)),
- TokenTree::Subtree(helpers)
- ] if comma.char == ',' && attributes.text == "attributes" =>
- {
- let helpers = helpers.token_trees.iter()
- .filter(|tt| !matches!(tt, TokenTree::Leaf(Leaf::Punct(comma)) if comma.char == ','))
- .map(|tt| {
- match tt {
- TokenTree::Leaf(Leaf::Ident(helper)) => Some(helper.as_name()),
- _ => None
- }
- })
- .collect::<Option<Box<[_]>>>()?;
-
- Some(ProcMacroDef {
- name: trait_name.as_name(),
- kind: ProcMacroKind::CustomDerive { helpers },
- })
- }
-
- _ => {
- tracing::trace!("malformed `#[proc_macro_derive]`: {}", derive);
- None
- }
+ if def.is_none() {
+ tracing::trace!("malformed `#[proc_macro_derive]`: {}", derive);
}
+
+ def
} else {
None
}
}
}
+
+// This fn is intended for `#[proc_macro_derive(..)]` and `#[rustc_builtin_macro(..)]`, which have
+// the same strucuture.
+#[rustfmt::skip]
+pub(crate) fn parse_macro_name_and_helper_attrs(tt: &[TokenTree]) -> Option<(Name, Box<[Name]>)> {
+ match tt {
+ // `#[proc_macro_derive(Trait)]`
+ // `#[rustc_builtin_macro(Trait)]`
+ [TokenTree::Leaf(Leaf::Ident(trait_name))] => Some((trait_name.as_name(), Box::new([]))),
+
+ // `#[proc_macro_derive(Trait, attributes(helper1, helper2, ...))]`
+ // `#[rustc_builtin_macro(Trait, attributes(helper1, helper2, ...))]`
+ [
+ TokenTree::Leaf(Leaf::Ident(trait_name)),
+ TokenTree::Leaf(Leaf::Punct(comma)),
+ TokenTree::Leaf(Leaf::Ident(attributes)),
+ TokenTree::Subtree(helpers)
+ ] if comma.char == ',' && attributes.text == "attributes" =>
+ {
+ let helpers = helpers
+ .token_trees
+ .iter()
+ .filter(
+ |tt| !matches!(tt, TokenTree::Leaf(Leaf::Punct(comma)) if comma.char == ','),
+ )
+ .map(|tt| match tt {
+ TokenTree::Leaf(Leaf::Ident(helper)) => Some(helper.as_name()),
+ _ => None,
+ })
+ .collect::<Option<Box<[_]>>>()?;
+
+ Some((trait_name.as_name(), helpers))
+ }
+
+ _ => None,
+ }
+}
diff --git a/crates/hir-def/src/nameres/tests/macros.rs b/crates/hir-def/src/nameres/tests/macros.rs
index 3ece1379ad..fe0ad4f386 100644
--- a/crates/hir-def/src/nameres/tests/macros.rs
+++ b/crates/hir-def/src/nameres/tests/macros.rs
@@ -823,6 +823,28 @@ fn derive() {}
}
#[test]
+fn resolves_derive_helper_rustc_builtin_macro() {
+ cov_mark::check!(resolved_derive_helper);
+ // This is NOT the correct usage of `default` helper attribute, but we don't resolve helper
+ // attributes on non mod items in hir nameres.
+ check(
+ r#"
+//- minicore: derive, default
+#[derive(Default)]
+#[default]
+enum E {
+ A,
+ B,
+}
+"#,
+ expect![[r#"
+ crate
+ E: t
+ "#]],
+ );
+}
+
+#[test]
fn unresolved_attr_with_cfg_attr_hang() {
// Another regression test for https://github.com/rust-lang/rust-analyzer/issues/8905
check(
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index cbbcaebb42..5f36ce62f8 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -2349,12 +2349,14 @@ impl DeriveHelper {
pub fn name(&self, db: &dyn HirDatabase) -> Name {
match self.derive {
- MacroId::Macro2Id(_) => None,
+ MacroId::Macro2Id(it) => {
+ db.macro2_data(it).helpers.as_deref().and_then(|it| it.get(self.idx)).cloned()
+ }
MacroId::MacroRulesId(_) => None,
MacroId::ProcMacroId(proc_macro) => db
.proc_macro_data(proc_macro)
.helpers
- .as_ref()
+ .as_deref()
.and_then(|it| it.get(self.idx))
.cloned(),
}
diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs
index 40a330a6bd..013fd6b4fd 100644
--- a/crates/test-utils/src/minicore.rs
+++ b/crates/test-utils/src/minicore.rs
@@ -112,7 +112,7 @@ pub mod default {
fn default() -> Self;
}
// region:derive
- #[rustc_builtin_macro]
+ #[rustc_builtin_macro(Default, attributes(default))]
pub macro Default($item:item) {}
// endregion:derive
}