mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-29 06:23:25 +00:00
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.
This commit is contained in:
commit
df07c8fafa
6 changed files with 113 additions and 40 deletions
|
@ -13,7 +13,9 @@ use crate::{
|
||||||
intern::Interned,
|
intern::Interned,
|
||||||
item_tree::{self, AssocItem, FnFlags, ItemTree, ItemTreeId, ModItem, Param, TreeId},
|
item_tree::{self, AssocItem, FnFlags, ItemTree, ItemTreeId, ModItem, Param, TreeId},
|
||||||
nameres::{
|
nameres::{
|
||||||
attr_resolution::ResolvedAttr, diagnostics::DefDiagnostic, proc_macro::ProcMacroKind,
|
attr_resolution::ResolvedAttr,
|
||||||
|
diagnostics::DefDiagnostic,
|
||||||
|
proc_macro::{parse_macro_name_and_helper_attrs, ProcMacroKind},
|
||||||
DefMap,
|
DefMap,
|
||||||
},
|
},
|
||||||
type_ref::{TraitRef, TypeBound, TypeRef},
|
type_ref::{TraitRef, TypeBound, TypeRef},
|
||||||
|
@ -348,6 +350,10 @@ impl ImplData {
|
||||||
pub struct Macro2Data {
|
pub struct Macro2Data {
|
||||||
pub name: Name,
|
pub name: Name,
|
||||||
pub visibility: RawVisibility,
|
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 {
|
impl Macro2Data {
|
||||||
|
@ -356,9 +362,18 @@ impl Macro2Data {
|
||||||
let item_tree = loc.id.item_tree(db);
|
let item_tree = loc.id.item_tree(db);
|
||||||
let makro = &item_tree[loc.id.value];
|
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 {
|
Arc::new(Macro2Data {
|
||||||
name: makro.name.clone(),
|
name: makro.name.clone(),
|
||||||
visibility: item_tree[makro.visibility].clone(),
|
visibility: item_tree[makro.visibility].clone(),
|
||||||
|
helpers,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ use crate::{
|
||||||
diagnostics::DefDiagnostic,
|
diagnostics::DefDiagnostic,
|
||||||
mod_resolution::ModDir,
|
mod_resolution::ModDir,
|
||||||
path_resolution::ReachedFixedPoint,
|
path_resolution::ReachedFixedPoint,
|
||||||
proc_macro::{ProcMacroDef, ProcMacroKind},
|
proc_macro::{parse_macro_name_and_helper_attrs, ProcMacroDef, ProcMacroKind},
|
||||||
BuiltinShadowMode, DefMap, ModuleData, ModuleOrigin, ResolveMode,
|
BuiltinShadowMode, DefMap, ModuleData, ModuleOrigin, ResolveMode,
|
||||||
},
|
},
|
||||||
path::{ImportAlias, ModPath, PathKind},
|
path::{ImportAlias, ModPath, PathKind},
|
||||||
|
@ -2005,6 +2005,7 @@ impl ModCollector<'_, '_> {
|
||||||
let ast_id = InFile::new(self.file_id(), mac.ast_id.upcast());
|
let ast_id = InFile::new(self.file_id(), mac.ast_id.upcast());
|
||||||
|
|
||||||
// Case 1: builtin macros
|
// 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 attrs = self.item_tree.attrs(self.def_collector.db, krate, ModItem::from(id).into());
|
||||||
let expander = if attrs.by_key("rustc_builtin_macro").exists() {
|
let expander = if attrs.by_key("rustc_builtin_macro").exists() {
|
||||||
if let Some(expander) = find_builtin_macro(&mac.name) {
|
if let Some(expander) = find_builtin_macro(&mac.name) {
|
||||||
|
@ -2013,6 +2014,25 @@ impl ModCollector<'_, '_> {
|
||||||
Either::Right(it) => MacroExpander::BuiltInEager(it),
|
Either::Right(it) => MacroExpander::BuiltInEager(it),
|
||||||
}
|
}
|
||||||
} else if let Some(expander) = find_builtin_derive(&mac.name) {
|
} 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)
|
MacroExpander::BuiltInDerive(expander)
|
||||||
} else if let Some(expander) = find_builtin_attr(&mac.name) {
|
} else if let Some(expander) = find_builtin_attr(&mac.name) {
|
||||||
MacroExpander::BuiltInAttr(expander)
|
MacroExpander::BuiltInAttr(expander)
|
||||||
|
@ -2037,6 +2057,12 @@ impl ModCollector<'_, '_> {
|
||||||
macro_id,
|
macro_id,
|
||||||
&self.item_tree[mac.visibility],
|
&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) {
|
fn collect_macro_call(&mut self, mac: &MacroCall, container: ItemContainerId) {
|
||||||
|
|
|
@ -37,15 +37,31 @@ impl Attrs {
|
||||||
Some(ProcMacroDef { name: func_name.clone(), kind: ProcMacroKind::Attr })
|
Some(ProcMacroDef { name: func_name.clone(), kind: ProcMacroKind::Attr })
|
||||||
} else if self.by_key("proc_macro_derive").exists() {
|
} else if self.by_key("proc_macro_derive").exists() {
|
||||||
let derive = self.by_key("proc_macro_derive").tt_values().next()?;
|
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 {
|
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)]`
|
// `#[proc_macro_derive(Trait)]`
|
||||||
[TokenTree::Leaf(Leaf::Ident(trait_name))] => Some(ProcMacroDef {
|
// `#[rustc_builtin_macro(Trait)]`
|
||||||
name: trait_name.as_name(),
|
[TokenTree::Leaf(Leaf::Ident(trait_name))] => Some((trait_name.as_name(), Box::new([]))),
|
||||||
kind: ProcMacroKind::CustomDerive { helpers: Box::new([]) },
|
|
||||||
}),
|
|
||||||
|
|
||||||
// `#[proc_macro_derive(Trait, attributes(helper1, helper2, ...))]`
|
// `#[proc_macro_derive(Trait, attributes(helper1, helper2, ...))]`
|
||||||
|
// `#[rustc_builtin_macro(Trait, attributes(helper1, helper2, ...))]`
|
||||||
[
|
[
|
||||||
TokenTree::Leaf(Leaf::Ident(trait_name)),
|
TokenTree::Leaf(Leaf::Ident(trait_name)),
|
||||||
TokenTree::Leaf(Leaf::Punct(comma)),
|
TokenTree::Leaf(Leaf::Punct(comma)),
|
||||||
|
@ -53,29 +69,21 @@ impl Attrs {
|
||||||
TokenTree::Subtree(helpers)
|
TokenTree::Subtree(helpers)
|
||||||
] if comma.char == ',' && attributes.text == "attributes" =>
|
] if comma.char == ',' && attributes.text == "attributes" =>
|
||||||
{
|
{
|
||||||
let helpers = helpers.token_trees.iter()
|
let helpers = helpers
|
||||||
.filter(|tt| !matches!(tt, TokenTree::Leaf(Leaf::Punct(comma)) if comma.char == ','))
|
.token_trees
|
||||||
.map(|tt| {
|
.iter()
|
||||||
match tt {
|
.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()),
|
TokenTree::Leaf(Leaf::Ident(helper)) => Some(helper.as_name()),
|
||||||
_ => None
|
_ => None,
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.collect::<Option<Box<[_]>>>()?;
|
.collect::<Option<Box<[_]>>>()?;
|
||||||
|
|
||||||
Some(ProcMacroDef {
|
Some((trait_name.as_name(), helpers))
|
||||||
name: trait_name.as_name(),
|
|
||||||
kind: ProcMacroKind::CustomDerive { helpers },
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {
|
_ => None,
|
||||||
tracing::trace!("malformed `#[proc_macro_derive]`: {}", derive);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
#[test]
|
||||||
fn unresolved_attr_with_cfg_attr_hang() {
|
fn unresolved_attr_with_cfg_attr_hang() {
|
||||||
// Another regression test for https://github.com/rust-lang/rust-analyzer/issues/8905
|
// Another regression test for https://github.com/rust-lang/rust-analyzer/issues/8905
|
||||||
|
|
|
@ -2349,12 +2349,14 @@ impl DeriveHelper {
|
||||||
|
|
||||||
pub fn name(&self, db: &dyn HirDatabase) -> Name {
|
pub fn name(&self, db: &dyn HirDatabase) -> Name {
|
||||||
match self.derive {
|
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::MacroRulesId(_) => None,
|
||||||
MacroId::ProcMacroId(proc_macro) => db
|
MacroId::ProcMacroId(proc_macro) => db
|
||||||
.proc_macro_data(proc_macro)
|
.proc_macro_data(proc_macro)
|
||||||
.helpers
|
.helpers
|
||||||
.as_ref()
|
.as_deref()
|
||||||
.and_then(|it| it.get(self.idx))
|
.and_then(|it| it.get(self.idx))
|
||||||
.cloned(),
|
.cloned(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,7 +112,7 @@ pub mod default {
|
||||||
fn default() -> Self;
|
fn default() -> Self;
|
||||||
}
|
}
|
||||||
// region:derive
|
// region:derive
|
||||||
#[rustc_builtin_macro]
|
#[rustc_builtin_macro(Default, attributes(default))]
|
||||||
pub macro Default($item:item) {}
|
pub macro Default($item:item) {}
|
||||||
// endregion:derive
|
// endregion:derive
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue