Parse and collect derive helpers for builtin derive macros

This commit is contained in:
Ryo Yoshida 2022-11-07 21:24:17 +09:00
parent f9bd487708
commit cf54b8c3a4
No known key found for this signature in database
GPG Key ID: E25698A930586171
4 changed files with 93 additions and 37 deletions

View File

@ -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) {

View File

@ -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,
}
}

View File

@ -822,6 +822,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

View File

@ -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
}