Skip to content

internal: Support builtin derive macro helper attributes #13730

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 6, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion crates/hir-def/src/data.rs
Original file line number Diff line number Diff line change
@@ -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,
})
}
}
28 changes: 27 additions & 1 deletion crates/hir-def/src/nameres/collector.rs
Original file line number Diff line number Diff line change
@@ -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) {
78 changes: 43 additions & 35 deletions crates/hir-def/src/nameres/proc_macro.rs
Original file line number Diff line number Diff line change
@@ -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,
}
}
22 changes: 22 additions & 0 deletions crates/hir-def/src/nameres/tests/macros.rs
Original file line number Diff line number Diff line change
@@ -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
6 changes: 4 additions & 2 deletions crates/hir/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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(),
}
2 changes: 1 addition & 1 deletion crates/test-utils/src/minicore.rs
Original file line number Diff line number Diff line change
@@ -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
}