10789: internal: Check for derive attributes by item path, not `derive` identifier r=Veykril a=Veykril

Prior we only checked if an attribute is the exact `derive` identifier and nothing else to collect derive attributes. That means when we encounter the following:
```rs
#[::core::macros::builtin::derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ModPath {
    pub kind: PathKind,
    segments: Vec<Name>,
}
```
We won't actually expand the derive attributes, but instead we just expand the `derive` attribute with our dummy identity expander.

The changes here make it so we actually lookup the attribute path, check if it is the derive attribute and then collect the derives.


Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
bors[bot] 2021-11-17 20:11:21 +00:00 committed by GitHub
commit fdd49b9713
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 189 additions and 270 deletions

View File

@ -8,7 +8,7 @@ use either::Either;
use hir_expand::{hygiene::Hygiene, name::AsName, AstId, InFile};
use itertools::Itertools;
use la_arena::ArenaMap;
use mbe::{syntax_node_to_token_tree, DelimiterKind};
use mbe::{syntax_node_to_token_tree, DelimiterKind, Punct};
use smallvec::{smallvec, SmallVec};
use syntax::{
ast::{self, AstNode, HasAttrs, IsString},
@ -722,41 +722,35 @@ impl Attr {
/// Parses this attribute as a `#[derive]`, returns an iterator that yields all contained paths
/// to derive macros.
///
/// Returns `None` when the attribute is not a well-formed `#[derive]` attribute.
/// Returns `None` when the attribute does not have a well-formed `#[derive]` attribute input.
pub(crate) fn parse_derive(&self) -> Option<impl Iterator<Item = ModPath>> {
if self.path.as_ident() != Some(&hir_expand::name![derive]) {
return None;
}
match self.input.as_deref() {
Some(AttrInput::TokenTree(args, _)) => {
let mut counter = 0;
let paths = args
.token_trees
.iter()
.group_by(move |tt| {
match tt {
tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ',' => {
counter += 1;
}
_ => {}
}
counter
})
.into_iter()
.map(|(_, tts)| {
let segments = tts.filter_map(|tt| match tt {
tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => Some(id.as_name()),
_ => None,
});
ModPath::from_segments(PathKind::Plain, segments)
})
.collect::<Vec<_>>();
Some(paths.into_iter())
if let Some(AttrInput::TokenTree(args, _)) = self.input.as_deref() {
if args.delimiter_kind() != Some(DelimiterKind::Parenthesis) {
return None;
}
_ => None,
let mut counter = 0;
let paths = args
.token_trees
.iter()
.group_by(move |tt| {
if let tt::TokenTree::Leaf(tt::Leaf::Punct(Punct { char: ',', .. })) = tt {
counter += 1;
}
counter
})
.into_iter()
.map(|(_, tts)| {
let segments = tts.filter_map(|tt| match tt {
tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => Some(id.as_name()),
_ => None,
});
ModPath::from_segments(PathKind::Plain, segments)
})
.collect::<Vec<_>>();
return Some(paths.into_iter());
}
None
}
pub fn string_value(&self) -> Option<&SmolStr> {

View File

@ -776,13 +776,10 @@ fn attr_macro_as_call_id(
macro_attr: &Attr,
db: &dyn db::DefDatabase,
krate: CrateId,
resolver: impl Fn(path::ModPath) -> Option<MacroDefId>,
def: Option<MacroDefId>,
) -> Result<MacroCallId, UnresolvedMacro> {
let attr_path = &item_attr.path;
let def = resolver(attr_path.clone())
.filter(MacroDefId::is_attribute)
.ok_or_else(|| UnresolvedMacro { path: attr_path.clone() })?;
let def = def.ok_or_else(|| UnresolvedMacro { path: attr_path.clone() })?;
let last_segment =
attr_path.segments().last().ok_or_else(|| UnresolvedMacro { path: attr_path.clone() })?;
let mut arg = match macro_attr.input.as_deref() {

View File

@ -26,12 +26,16 @@ fn test_copy_expand_in_core() {
check(
r#"
#[rustc_builtin_macro]
macro derive {}
#[rustc_builtin_macro]
macro Copy {}
#[derive(Copy)]
struct Foo;
"#,
expect![[r##"
#[rustc_builtin_macro]
macro derive {}
#[rustc_builtin_macro]
macro Copy {}
#[derive(Copy)]
struct Foo;

View File

@ -31,6 +31,7 @@ fn derive_censoring() {
check(
r#"
//- proc_macros: derive_identity
//- minicore:derive
#[attr1]
#[derive(Foo)]
#[derive(proc_macros::DeriveIdentity)]

View File

@ -9,7 +9,7 @@ use base_db::{CrateId, Edition, FileId, ProcMacroId};
use cfg::{CfgExpr, CfgOptions};
use hir_expand::{
ast_id_map::FileAstId,
builtin_attr_macro::{find_builtin_attr, is_builtin_test_or_bench_attr},
builtin_attr_macro::find_builtin_attr,
builtin_derive_macro::find_builtin_derive,
builtin_fn_macro::find_builtin_macro,
name::{name, AsName, Name},
@ -781,7 +781,7 @@ impl DefCollector<'_> {
}
fn resolve_extern_crate(&self, name: &Name) -> PerNs {
if name == &name!(self) {
if *name == name!(self) {
cov_mark::hit!(extern_crate_self_as);
let root = match self.def_map.block {
Some(_) => {
@ -1054,14 +1054,15 @@ impl DefCollector<'_> {
match &directive.kind {
MacroDirectiveKind::FnLike { ast_id, expand_to } => {
match macro_call_as_call_id(
let call_id = macro_call_as_call_id(
ast_id,
*expand_to,
self.db,
self.def_map.krate,
&resolver,
&mut |_err| (),
) {
);
match call_id {
Ok(Ok(call_id)) => {
resolved.push((directive.module_id, call_id, directive.depth));
res = ReachedFixedPoint::No;
@ -1071,13 +1072,14 @@ impl DefCollector<'_> {
}
}
MacroDirectiveKind::Derive { ast_id, derive_attr } => {
match derive_macro_as_call_id(
let call_id = derive_macro_as_call_id(
ast_id,
*derive_attr,
self.db,
self.def_map.krate,
&resolver,
) {
);
match call_id {
Ok(call_id) => {
self.def_map.modules[directive.module_id].scope.add_derive_macro_invoc(
ast_id.ast_id,
@ -1089,73 +1091,103 @@ impl DefCollector<'_> {
res = ReachedFixedPoint::No;
return false;
}
Err(UnresolvedMacro { .. }) => (),
Err(UnresolvedMacro { .. }) => {}
}
}
MacroDirectiveKind::Attr { ast_id, mod_item, attr } => {
let file_id = ast_id.ast_id.file_id;
let mut recollect_without = |collector: &mut Self, item_tree| {
// Remove the original directive since we resolved it.
let mod_dir = collector.mod_dirs[&directive.module_id].clone();
collector.skip_attrs.insert(InFile::new(file_id, *mod_item), attr.id);
ModCollector {
def_collector: collector,
macro_depth: directive.depth,
module_id: directive.module_id,
tree_id: TreeId::new(file_id, None),
item_tree,
mod_dir,
}
.collect(&[*mod_item]);
res = ReachedFixedPoint::No;
false
};
if let Some(ident) = ast_id.path.as_ident() {
if let Some(helpers) = self.derive_helpers_in_scope.get(&ast_id.ast_id) {
if helpers.contains(ident) {
cov_mark::hit!(resolved_derive_helper);
// Resolved to derive helper. Collect the item's attributes again,
// starting after the derive helper.
let file_id = ast_id.ast_id.file_id;
let item_tree = self.db.file_item_tree(file_id);
let mod_dir = self.mod_dirs[&directive.module_id].clone();
self.skip_attrs.insert(InFile::new(file_id, *mod_item), attr.id);
ModCollector {
def_collector: &mut *self,
macro_depth: directive.depth,
module_id: directive.module_id,
tree_id: TreeId::new(file_id, None),
item_tree: &item_tree,
mod_dir,
}
.collect(&[*mod_item]);
return recollect_without(self, &item_tree);
}
}
}
// Remove the original directive since we resolved it.
let def = resolver(ast_id.path.clone()).filter(MacroDefId::is_attribute);
if matches!(
def,
Some(MacroDefId { kind:MacroDefKind::BuiltInAttr(expander, _),.. })
if expander.is_derive()
) {
// Resolved to `#[derive]`
let file_id = ast_id.ast_id.file_id;
let item_tree = self.db.file_item_tree(file_id);
let ast_id: FileAstId<ast::Item> = match *mod_item {
ModItem::Struct(it) => item_tree[it].ast_id.upcast(),
ModItem::Union(it) => item_tree[it].ast_id.upcast(),
ModItem::Enum(it) => item_tree[it].ast_id.upcast(),
_ => {
// Cannot use derive on this item.
// FIXME: diagnose
res = ReachedFixedPoint::No;
return false;
}
};
match attr.parse_derive() {
Some(derive_macros) => {
for path in derive_macros {
let ast_id = AstIdWithPath::new(file_id, ast_id, path);
self.unresolved_macros.push(MacroDirective {
module_id: directive.module_id,
depth: directive.depth + 1,
kind: MacroDirectiveKind::Derive {
ast_id,
derive_attr: attr.id,
},
});
}
}
None => {
// FIXME: diagnose
tracing::debug!("malformed derive: {:?}", attr);
}
}
return recollect_without(self, &item_tree);
}
if !self.db.enable_proc_attr_macros() {
return true;
}
// Not resolved to a derive helper, so try to resolve as a macro.
match attr_macro_as_call_id(
ast_id,
attr,
self.db,
self.def_map.krate,
&resolver,
) {
// Not resolved to a derive helper or the derive attribute, so try to resolve as a normal attribute.
match attr_macro_as_call_id(ast_id, attr, self.db, self.def_map.krate, def) {
Ok(call_id) => {
let loc: MacroCallLoc = self.db.lookup_intern_macro_call(call_id);
// Skip #[test]/#[bench] expansion, which would merely result in more memory usage
// due to duplicating functions into macro expansions
if is_builtin_test_or_bench_attr(loc.def) {
let file_id = ast_id.ast_id.file_id;
if matches!(
loc.def.kind,
MacroDefKind::BuiltInAttr(expander, _)
if expander.is_test() || expander.is_bench()
) {
let item_tree = self.db.file_item_tree(file_id);
let mod_dir = self.mod_dirs[&directive.module_id].clone();
self.skip_attrs.insert(InFile::new(file_id, *mod_item), attr.id);
ModCollector {
def_collector: &mut *self,
macro_depth: directive.depth,
module_id: directive.module_id,
tree_id: TreeId::new(file_id, None),
item_tree: &item_tree,
mod_dir,
}
.collect(&[*mod_item]);
// Remove the original directive since we resolved it.
res = ReachedFixedPoint::No;
return false;
return recollect_without(self, &item_tree);
}
if let MacroDefKind::ProcMacro(exp, ..) = loc.def.kind {
@ -1171,21 +1203,7 @@ impl DefCollector<'_> {
let file_id = ast_id.ast_id.file_id;
let item_tree = self.db.file_item_tree(file_id);
let mod_dir = self.mod_dirs[&directive.module_id].clone();
self.skip_attrs
.insert(InFile::new(file_id, *mod_item), attr.id);
ModCollector {
def_collector: &mut *self,
macro_depth: directive.depth,
module_id: directive.module_id,
tree_id: TreeId::new(file_id, None),
item_tree: &item_tree,
mod_dir,
}
.collect(&[*mod_item]);
// Remove the macro directive.
return false;
return recollect_without(self, &item_tree);
}
}
@ -1281,7 +1299,7 @@ impl DefCollector<'_> {
for directive in &self.unresolved_macros {
match &directive.kind {
MacroDirectiveKind::FnLike { ast_id, expand_to } => {
match macro_call_as_call_id(
let macro_call_as_call_id = macro_call_as_call_id(
ast_id,
*expand_to,
self.db,
@ -1297,15 +1315,13 @@ impl DefCollector<'_> {
resolved_res.resolved_def.take_macros()
},
&mut |_| (),
) {
Ok(_) => (),
Err(UnresolvedMacro { path }) => {
self.def_map.diagnostics.push(DefDiagnostic::unresolved_macro_call(
directive.module_id,
ast_id.ast_id,
path,
));
}
);
if let Err(UnresolvedMacro { path }) = macro_call_as_call_id {
self.def_map.diagnostics.push(DefDiagnostic::unresolved_macro_call(
directive.module_id,
ast_id.ast_id,
path,
));
}
}
MacroDirectiveKind::Derive { .. } | MacroDirectiveKind::Attr { .. } => {
@ -1747,26 +1763,23 @@ impl ModCollector<'_, '_> {
});
for attr in iter {
if attr.path.as_ident() == Some(&hir_expand::name![derive]) {
self.collect_derive(attr, mod_item);
} else if self.is_builtin_or_registered_attr(&attr.path) {
if self.is_builtin_or_registered_attr(&attr.path) {
continue;
} else {
tracing::debug!("non-builtin attribute {}", attr.path);
let ast_id = AstIdWithPath::new(
self.file_id(),
mod_item.ast_id(self.item_tree),
attr.path.as_ref().clone(),
);
self.def_collector.unresolved_macros.push(MacroDirective {
module_id: self.module_id,
depth: self.macro_depth + 1,
kind: MacroDirectiveKind::Attr { ast_id, attr: attr.clone(), mod_item },
});
return Err(());
}
tracing::debug!("non-builtin attribute {}", attr.path);
let ast_id = AstIdWithPath::new(
self.file_id(),
mod_item.ast_id(self.item_tree),
attr.path.as_ref().clone(),
);
self.def_collector.unresolved_macros.push(MacroDirective {
module_id: self.module_id,
depth: self.macro_depth + 1,
kind: MacroDirectiveKind::Attr { ast_id, attr: attr.clone(), mod_item },
});
return Err(());
}
Ok(())
@ -1800,36 +1813,6 @@ impl ModCollector<'_, '_> {
false
}
fn collect_derive(&mut self, attr: &Attr, mod_item: ModItem) {
let ast_id: FileAstId<ast::Item> = match mod_item {
ModItem::Struct(it) => self.item_tree[it].ast_id.upcast(),
ModItem::Union(it) => self.item_tree[it].ast_id.upcast(),
ModItem::Enum(it) => self.item_tree[it].ast_id.upcast(),
_ => {
// Cannot use derive on this item.
// FIXME: diagnose
return;
}
};
match attr.parse_derive() {
Some(derive_macros) => {
for path in derive_macros {
let ast_id = AstIdWithPath::new(self.file_id(), ast_id, path);
self.def_collector.unresolved_macros.push(MacroDirective {
module_id: self.module_id,
depth: self.macro_depth + 1,
kind: MacroDirectiveKind::Derive { ast_id, derive_attr: attr.id },
});
}
}
None => {
// FIXME: diagnose
tracing::debug!("malformed derive: {:?}", attr);
}
}
}
/// If `attrs` registers a procedural macro, collects its definition.
fn collect_proc_macro_def(&mut self, func_name: &Name, ast_id: AstId<ast::Fn>, attrs: &Attrs) {
// FIXME: this should only be done in the root module of `proc-macro` crates, not everywhere

View File

@ -669,19 +669,20 @@ pub struct bar;
fn expand_derive() {
let map = compute_crate_def_map(
r#"
//- /main.rs crate:main deps:core
use core::Copy;
//- /main.rs crate:main deps:core
use core::Copy;
#[derive(Copy, core::Clone)]
struct Foo;
#[core::derive(Copy, core::Clone)]
struct Foo;
//- /core.rs crate:core
#[rustc_builtin_macro]
pub macro Copy {}
#[rustc_builtin_macro]
pub macro Clone {}
"#,
//- /core.rs crate:core
#[rustc_builtin_macro]
pub macro derive($item:item) {}
#[rustc_builtin_macro]
pub macro Copy {}
#[rustc_builtin_macro]
pub macro Clone {}
"#,
);
assert_eq!(map.modules[map.root].scope.impls().len(), 2);
}
@ -712,17 +713,19 @@ fn builtin_derive_with_unresolved_attributes_fall_back() {
cov_mark::check!(unresolved_attribute_fallback);
let map = compute_crate_def_map(
r#"
//- /main.rs crate:main deps:core
use core::Clone;
//- /main.rs crate:main deps:core
use core::{Clone, derive};
#[derive(Clone)]
#[unresolved]
struct Foo;
#[derive(Clone)]
#[unresolved]
struct Foo;
//- /core.rs crate:core
#[rustc_builtin_macro]
pub macro Clone {}
"#,
//- /core.rs crate:core
#[rustc_builtin_macro]
pub macro derive($item:item) {}
#[rustc_builtin_macro]
pub macro Clone {}
"#,
);
assert_eq!(map.modules[map.root].scope.impls().len(), 1);
}
@ -799,6 +802,9 @@ fn resolves_derive_helper() {
check(
r#"
//- /main.rs crate:main deps:proc
#[rustc_builtin_macro]
pub macro derive($item:item) {}
#[derive(proc::Derive)]
#[helper]
#[unresolved]
@ -811,6 +817,7 @@ fn derive() {}
expect![[r#"
crate
S: t v
derive: m
"#]],
);
}

View File

@ -36,6 +36,18 @@ macro_rules! register_builtin {
};
}
impl BuiltinAttrExpander {
pub fn is_derive(self) -> bool {
matches!(self, BuiltinAttrExpander::Derive)
}
pub fn is_test(self) -> bool {
matches!(self, BuiltinAttrExpander::Test)
}
pub fn is_bench(self) -> bool {
matches!(self, BuiltinAttrExpander::Bench)
}
}
register_builtin! {
(bench, Bench) => dummy_attr_expand,
(cfg_accessible, CfgAccessible) => dummy_attr_expand,
@ -46,16 +58,6 @@ register_builtin! {
(test_case, TestCase) => dummy_attr_expand
}
pub fn is_builtin_test_or_bench_attr(makro: MacroDefId) -> bool {
match makro.kind {
MacroDefKind::BuiltInAttr(expander, ..) => {
BuiltinAttrExpander::find_by_name(&name!(test)) == Some(expander)
|| BuiltinAttrExpander::find_by_name(&name!(bench)) == Some(expander)
}
_ => false,
}
}
pub fn find_builtin_attr(
ident: &name::Name,
krate: CrateId,

View File

@ -974,61 +974,12 @@ fn infer_builtin_macros_env() {
fn infer_derive_clone_simple() {
check_types(
r#"
//- /main.rs crate:main deps:core
//- minicore: derive, clone
#[derive(Clone)]
struct S;
fn test() {
S.clone();
} //^^^^^^^^^ S
//- /lib.rs crate:core
pub mod prelude {
pub mod rust_2018 {
#[rustc_builtin_macro]
pub macro Clone {}
pub use crate::clone::Clone;
}
}
pub mod clone {
pub trait Clone {
fn clone(&self) -> Self;
}
}
"#,
);
}
#[test]
fn infer_derive_clone_in_core() {
check_types(
r#"
//- /lib.rs crate:core
#[prelude_import]
use prelude::rust_2018::*;
pub mod prelude {
pub mod rust_2018 {
#[rustc_builtin_macro]
pub macro Clone {}
pub use crate::clone::Clone;
}
}
pub mod clone {
pub trait Clone {
fn clone(&self) -> Self;
}
}
#[derive(Clone)]
pub struct S;
//- /main.rs crate:main deps:core
use core::S;
fn test() {
S.clone();
} //^^^^^^^^^ S
"#,
);
}
@ -1037,7 +988,7 @@ fn test() {
fn infer_derive_clone_with_params() {
check_types(
r#"
//- /main.rs crate:main deps:core
//- minicore: clone, derive
#[derive(Clone)]
struct S;
#[derive(Clone)]
@ -1048,21 +999,6 @@ fn test() {
x;
//^ (Wrapper<S>, {unknown})
}
//- /lib.rs crate:core
pub mod prelude {
pub mod rust_2018 {
#[rustc_builtin_macro]
pub macro Clone {}
pub use crate::clone::Clone;
}
}
pub mod clone {
pub trait Clone {
fn clone(&self) -> Self;
}
}
"#,
);
}
@ -1072,7 +1008,7 @@ fn infer_custom_derive_simple() {
// FIXME: this test current now do nothing
check_types(
r#"
//- /main.rs crate:main
//- minicore: derive
use foo::Foo;
#[derive(Foo)]

View File

@ -367,9 +367,7 @@ fn main() {
check(
r#"
//- proc_macros: identity
#[rustc_builtin_macro]
pub macro Clone {}
//- minicore: clone, derive
#[proc_macros::identity]
#[derive(C$0lone)]
@ -377,7 +375,7 @@ struct Foo {}
"#,
expect![[r#"
Clone
impl< >crate::clone::Clone for Foo< >{}
impl< >core::clone::Clone for Foo< >{}
"#]],
);
@ -387,10 +385,7 @@ struct Foo {}
fn macro_expand_derive2() {
check(
r#"
#[rustc_builtin_macro]
pub macro Clone {}
#[rustc_builtin_macro]
pub macro Copy {}
//- minicore: copy, clone, derive
#[derive(Cop$0y)]
#[derive(Clone)]
@ -398,7 +393,7 @@ struct Foo {}
"#,
expect![[r#"
Copy
impl< >crate::marker::Copy for Foo< >{}
impl< >core::marker::Copy for Foo< >{}
"#]],
);
@ -408,19 +403,16 @@ struct Foo {}
fn macro_expand_derive_multi() {
check(
r#"
#[rustc_builtin_macro]
pub macro Clone {}
#[rustc_builtin_macro]
pub macro Copy {}
//- minicore: copy, clone, derive
#[derive(Cop$0y, Clone)]
struct Foo {}
"#,
expect![[r#"
Copy, Clone
impl< >crate::marker::Copy for Foo< >{}
impl< >core::marker::Copy for Foo< >{}
impl< >crate::clone::Clone for Foo< >{}
impl< >core::clone::Clone for Foo< >{}
"#]],
);

View File

@ -805,6 +805,9 @@ bar = {path = "../bar"}
//- /foo/src/main.rs
use bar::Bar;
#[rustc_builtin_macro]
macro derive($item:item) {}
trait Bar {
fn bar();
}
@ -875,7 +878,7 @@ pub fn foo(_input: TokenStream) -> TokenStream {
let res = server.send_request::<HoverRequest>(HoverParams {
text_document_position_params: TextDocumentPositionParams::new(
server.doc_id("foo/src/main.rs"),
Position::new(7, 9),
Position::new(10, 9),
),
work_done_progress_params: Default::default(),
});