Fixes for builtin derive expansions

- Do not store the `MacroCallId` of the "real" expansion anywhere, so that the IDE layer could not expand it by mistake
 - Fix a stupid bug where we used the directive of the `derive` itself instead of of the macro, leading us to re-expand it again and again.
This commit is contained in:
Chayim Refael Friedman 2026-01-08 09:25:28 +02:00
parent 6acaa27fa6
commit 5d8a7daf2a
10 changed files with 211 additions and 101 deletions

View file

@ -8,7 +8,8 @@ use intern::{Symbol, sym};
use tt::TextRange;
use crate::{
AdtId, BuiltinDeriveImplId, BuiltinDeriveImplLoc, FunctionId, HasModule, db::DefDatabase,
AdtId, BuiltinDeriveImplId, BuiltinDeriveImplLoc, FunctionId, HasModule, MacroId,
db::DefDatabase, lang_item::LangItems,
};
macro_rules! declare_enum {
@ -86,6 +87,25 @@ declare_enum!(
DispatchFromDyn => [],
);
impl BuiltinDeriveImplTrait {
pub fn derive_macro(self, lang_items: &LangItems) -> Option<MacroId> {
match self {
BuiltinDeriveImplTrait::Copy => lang_items.CopyDerive,
BuiltinDeriveImplTrait::Clone => lang_items.CloneDerive,
BuiltinDeriveImplTrait::Default => lang_items.DefaultDerive,
BuiltinDeriveImplTrait::Debug => lang_items.DebugDerive,
BuiltinDeriveImplTrait::Hash => lang_items.HashDerive,
BuiltinDeriveImplTrait::Ord => lang_items.OrdDerive,
BuiltinDeriveImplTrait::PartialOrd => lang_items.PartialOrdDerive,
BuiltinDeriveImplTrait::Eq => lang_items.EqDerive,
BuiltinDeriveImplTrait::PartialEq => lang_items.PartialEqDerive,
BuiltinDeriveImplTrait::CoerceUnsized | BuiltinDeriveImplTrait::DispatchFromDyn => {
lang_items.CoercePointeeDerive
}
}
}
}
impl BuiltinDeriveImplMethod {
pub fn trait_method(
self,

View file

@ -27,14 +27,15 @@
pub mod keys {
use std::marker::PhantomData;
use either::Either;
use hir_expand::{MacroCallId, attrs::AttrId};
use rustc_hash::FxHashMap;
use syntax::{AstNode, AstPtr, ast};
use crate::{
BlockId, ConstId, EnumId, EnumVariantId, ExternBlockId, ExternCrateId, FieldId, FunctionId,
ImplId, LifetimeParamId, Macro2Id, MacroRulesId, ProcMacroId, StaticId, StructId, TraitId,
TypeAliasId, TypeOrConstParamId, UnionId, UseId,
BlockId, BuiltinDeriveImplId, ConstId, EnumId, EnumVariantId, ExternBlockId, ExternCrateId,
FieldId, FunctionId, ImplId, LifetimeParamId, Macro2Id, MacroRulesId, ProcMacroId,
StaticId, StructId, TraitId, TypeAliasId, TypeOrConstParamId, UnionId, UseId,
dyn_map::{DynMap, Policy},
};
@ -71,7 +72,8 @@ pub mod keys {
(
AttrId,
/* derive() */ MacroCallId,
/* actual derive macros */ Box<[Option<MacroCallId>]>,
/* actual derive macros */
Box<[Option<Either<MacroCallId, BuiltinDeriveImplId>>]>,
),
> = Key::new();

View file

@ -4,6 +4,7 @@
use std::{fmt, sync::LazyLock};
use base_db::Crate;
use either::Either;
use hir_expand::{AstId, MacroCallId, attrs::AttrId, name::Name};
use indexmap::map::Entry;
use itertools::Itertools;
@ -199,7 +200,7 @@ struct DeriveMacroInvocation {
attr_id: AttrId,
/// The `#[derive]` call
attr_call_id: MacroCallId,
derive_call_ids: SmallVec<[Option<MacroCallId>; 4]>,
derive_call_ids: SmallVec<[Option<Either<MacroCallId, BuiltinDeriveImplId>>; 4]>,
}
pub(crate) static BUILTIN_SCOPE: LazyLock<FxIndexMap<Name, PerNs>> = LazyLock::new(|| {
@ -345,7 +346,9 @@ impl ItemScope {
pub fn all_macro_calls(&self) -> impl Iterator<Item = MacroCallId> + '_ {
self.macro_invocations.values().copied().chain(self.attr_macros.values().copied()).chain(
self.derive_macros.values().flat_map(|it| {
it.iter().flat_map(|it| it.derive_call_ids.iter().copied().flatten())
it.iter().flat_map(|it| {
it.derive_call_ids.iter().copied().flatten().flat_map(|it| it.left())
})
}),
)
}
@ -379,6 +382,10 @@ impl ItemScope {
self.types.get(name).map(|item| (item.def, item.vis))
}
pub(crate) fn makro(&self, name: &Name) -> Option<MacroId> {
self.macros.get(name).map(|item| item.def)
}
/// XXX: this is O(N) rather than O(1), try to not introduce new usages.
pub(crate) fn name_of(&self, item: ItemInNs) -> Option<(&Name, Visibility, /*declared*/ bool)> {
match item {
@ -519,7 +526,7 @@ impl ItemScope {
pub(crate) fn set_derive_macro_invoc(
&mut self,
adt: AstId<ast::Adt>,
call: MacroCallId,
call: Either<MacroCallId, BuiltinDeriveImplId>,
id: AttrId,
idx: usize,
) {
@ -539,7 +546,7 @@ impl ItemScope {
adt: AstId<ast::Adt>,
attr_id: AttrId,
attr_call_id: MacroCallId,
mut derive_call_ids: SmallVec<[Option<MacroCallId>; 4]>,
mut derive_call_ids: SmallVec<[Option<Either<MacroCallId, BuiltinDeriveImplId>>; 4]>,
) {
derive_call_ids.shrink_to_fit();
self.derive_macros.entry(adt).or_default().push(DeriveMacroInvocation {
@ -554,7 +561,9 @@ impl ItemScope {
) -> impl Iterator<
Item = (
AstId<ast::Adt>,
impl Iterator<Item = (AttrId, MacroCallId, &[Option<MacroCallId>])>,
impl Iterator<
Item = (AttrId, MacroCallId, &[Option<Either<MacroCallId, BuiltinDeriveImplId>>]),
>,
),
> + '_ {
self.derive_macros.iter().map(|(k, v)| {

View file

@ -7,8 +7,8 @@ use intern::{Symbol, sym};
use stdx::impl_from;
use crate::{
AdtId, AssocItemId, AttrDefId, Crate, EnumId, EnumVariantId, FunctionId, ImplId, ModuleDefId,
StaticId, StructId, TraitId, TypeAliasId, UnionId,
AdtId, AssocItemId, AttrDefId, Crate, EnumId, EnumVariantId, FunctionId, ImplId, MacroId,
ModuleDefId, StaticId, StructId, TraitId, TypeAliasId, UnionId,
attrs::AttrFlags,
db::DefDatabase,
nameres::{DefMap, assoc::TraitItems, crate_def_map, crate_local_def_map},
@ -99,7 +99,7 @@ pub fn crate_lang_items(db: &dyn DefDatabase, krate: Crate) -> Option<Box<LangIt
}
if matches!(krate.data(db).origin, base_db::CrateOrigin::Lang(base_db::LangCrateOrigin::Core)) {
lang_items.fill_non_lang_core_traits(db, crate_def_map);
lang_items.fill_non_lang_core_items(db, crate_def_map);
}
if lang_items.is_empty() { None } else { Some(Box::new(lang_items)) }
@ -169,6 +169,27 @@ fn resolve_core_trait(
Some(trait_)
}
fn resolve_core_macro(
db: &dyn DefDatabase,
core_def_map: &DefMap,
modules: &[Symbol],
name: Symbol,
) -> Option<MacroId> {
let mut current = &core_def_map[core_def_map.root];
for module in modules {
let Some((ModuleDefId::ModuleId(cur), _)) =
current.scope.type_(&Name::new_symbol_root(module.clone()))
else {
return None;
};
if cur.krate(db) != core_def_map.krate() || cur.block(db) != core_def_map.block_id() {
return None;
}
current = &core_def_map[cur];
}
current.scope.makro(&Name::new_symbol_root(name))
}
#[salsa::tracked(returns(as_deref))]
pub(crate) fn crate_notable_traits(db: &dyn DefDatabase, krate: Crate) -> Option<Box<[TraitId]>> {
let mut traits = Vec::new();
@ -195,7 +216,11 @@ macro_rules! language_item_table {
@non_lang_core_traits:
$( core::$($non_lang_module:ident)::*, $non_lang_trait:ident; )*
$( core::$($non_lang_trait_module:ident)::*, $non_lang_trait:ident; )*
@non_lang_core_macros:
$( core::$($non_lang_macro_module:ident)::*, $non_lang_macro:ident, $non_lang_macro_field:ident; )*
) => {
#[allow(non_snake_case)] // FIXME: Should we remove this?
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
@ -207,6 +232,9 @@ macro_rules! language_item_table {
$(
pub $non_lang_trait: Option<TraitId>,
)*
$(
pub $non_lang_macro_field: Option<MacroId>,
)*
}
impl LangItems {
@ -218,6 +246,7 @@ macro_rules! language_item_table {
fn merge_prefer_self(&mut self, other: &Self) {
$( self.$lang_item = self.$lang_item.or(other.$lang_item); )*
$( self.$non_lang_trait = self.$non_lang_trait.or(other.$non_lang_trait); )*
$( self.$non_lang_macro_field = self.$non_lang_macro_field.or(other.$non_lang_macro_field); )*
}
fn assign_lang_item(&mut self, name: Symbol, target: LangItemTarget) {
@ -233,8 +262,9 @@ macro_rules! language_item_table {
}
}
fn fill_non_lang_core_traits(&mut self, db: &dyn DefDatabase, core_def_map: &DefMap) {
$( self.$non_lang_trait = resolve_core_trait(db, core_def_map, &[ $(sym::$non_lang_module),* ], sym::$non_lang_trait); )*
fn fill_non_lang_core_items(&mut self, db: &dyn DefDatabase, core_def_map: &DefMap) {
$( self.$non_lang_trait = resolve_core_trait(db, core_def_map, &[ $(sym::$non_lang_trait_module),* ], sym::$non_lang_trait); )*
$( self.$non_lang_macro_field = resolve_core_macro(db, core_def_map, &[ $(sym::$non_lang_macro_module),* ], sym::$non_lang_macro); )*
}
}
@ -479,4 +509,16 @@ language_item_table! { LangItems =>
core::hash, Hash;
core::cmp, Ord;
core::cmp, Eq;
@non_lang_core_macros:
core::default, Default, DefaultDerive;
core::fmt, Debug, DebugDerive;
core::hash, Hash, HashDerive;
core::cmp, PartialOrd, PartialOrdDerive;
core::cmp, Ord, OrdDerive;
core::cmp, PartialEq, PartialEqDerive;
core::cmp, Eq, EqDerive;
core::marker, CoercePointee, CoercePointeeDerive;
core::marker, Copy, CopyDerive;
core::clone, Clone, CloneDerive;
}

View file

@ -61,6 +61,7 @@ mod tests;
use std::ops::{Deref, DerefMut, Index, IndexMut};
use base_db::Crate;
use either::Either;
use hir_expand::{
EditionedFileId, ErasedAstId, HirFileId, InFile, MacroCallId, mod_path::ModPath, name::Name,
proc_macro::ProcMacroKind,
@ -75,8 +76,8 @@ use triomphe::Arc;
use tt::TextRange;
use crate::{
AstId, BlockId, BlockLoc, ExternCrateId, FunctionId, FxIndexMap, Lookup, MacroCallStyles,
MacroExpander, MacroId, ModuleId, ModuleIdLt, ProcMacroId, UseId,
AstId, BlockId, BlockLoc, BuiltinDeriveImplId, ExternCrateId, FunctionId, FxIndexMap, Lookup,
MacroCallStyles, MacroExpander, MacroId, ModuleId, ModuleIdLt, ProcMacroId, UseId,
db::DefDatabase,
item_scope::{BuiltinShadowMode, ItemScope},
item_tree::TreeId,
@ -192,7 +193,8 @@ pub struct DefMap {
/// Tracks which custom derives are in scope for an item, to allow resolution of derive helper
/// attributes.
// FIXME: Figure out a better way for the IDE layer to resolve these?
derive_helpers_in_scope: FxHashMap<AstId<ast::Item>, Vec<(Name, MacroId, MacroCallId)>>,
derive_helpers_in_scope:
FxHashMap<AstId<ast::Item>, Vec<(Name, MacroId, Either<MacroCallId, BuiltinDeriveImplId>)>>,
/// A mapping from [`hir_expand::MacroDefId`] to [`crate::MacroId`].
pub macro_def_to_macro_id: FxHashMap<ErasedAstId, MacroId>,
@ -540,7 +542,7 @@ impl DefMap {
pub fn derive_helpers_in_scope(
&self,
id: AstId<ast::Adt>,
) -> Option<&[(Name, MacroId, MacroCallId)]> {
) -> Option<&[(Name, MacroId, Either<MacroCallId, BuiltinDeriveImplId>)]> {
self.derive_helpers_in_scope.get(&id.map(|it| it.upcast())).map(Deref::deref)
}

View file

@ -3,7 +3,7 @@
//! `DefCollector::collect` contains the fixed-point iteration loop which
//! resolves imports and expands macros.
use std::{iter, mem};
use std::{iter, mem, ops::Range};
use base_db::{BuiltDependency, Crate, CrateOrigin, LangCrateOrigin};
use cfg::{CfgAtom, CfgExpr, CfgOptions};
@ -226,6 +226,7 @@ struct DeferredBuiltinDerive {
container: ItemContainerId,
derive_attr_id: AttrId,
derive_index: u32,
helpers_range: Range<usize>,
}
/// Walks the tree of module recursively
@ -1354,7 +1355,7 @@ impl<'db> DefCollector<'db> {
if let Ok((macro_id, def_id, call_id)) = id {
self.def_map.modules[directive.module_id].scope.set_derive_macro_invoc(
ast_id.ast_id,
call_id,
Either::Left(call_id),
*derive_attr,
*derive_pos,
);
@ -1369,7 +1370,7 @@ impl<'db> DefCollector<'db> {
.extend(izip!(
helpers.iter().cloned(),
iter::repeat(macro_id),
iter::repeat(call_id),
iter::repeat(Either::Left(call_id)),
));
}
}
@ -1492,6 +1493,8 @@ impl<'db> DefCollector<'db> {
Interned::new(path),
);
derive_call_ids.push(None);
// Try to resolve the derive immediately. If we succeed, we can also use the fast path
// for builtin derives. If not, we cannot use it, as it can cause the ADT to become
// interned while the derive is still unresolved, which will cause it to get forgotten.
@ -1506,23 +1509,42 @@ impl<'db> DefCollector<'db> {
call_id,
);
let ast_id_without_path = ast_id.ast_id;
let directive = MacroDirective {
module_id: directive.module_id,
depth: directive.depth + 1,
kind: MacroDirectiveKind::Derive {
ast_id,
derive_attr: *attr_id,
derive_pos: idx,
ctxt: call_site.ctx,
derive_macro_id: call_id,
},
container: directive.container,
};
if let Ok((macro_id, def_id, call_id)) = id {
derive_call_ids.push(Some(call_id));
let (mut helpers_start, mut helpers_end) = (0, 0);
// Record its helper attributes.
if def_id.krate != self.def_map.krate {
let def_map = crate_def_map(self.db, def_id.krate);
if let Some(helpers) =
def_map.data.exported_derives.get(&macro_id)
{
self.def_map
let derive_helpers = self
.def_map
.derive_helpers_in_scope
.entry(ast_id.ast_id.map(|it| it.upcast()))
.or_default()
.extend(izip!(
helpers.iter().cloned(),
iter::repeat(macro_id),
iter::repeat(call_id),
));
.entry(
ast_id_without_path.map(|it| it.upcast()),
)
.or_default();
helpers_start = derive_helpers.len();
derive_helpers.extend(izip!(
helpers.iter().cloned(),
iter::repeat(macro_id),
iter::repeat(Either::Left(call_id)),
));
helpers_end = derive_helpers.len();
}
}
@ -1531,7 +1553,7 @@ impl<'db> DefCollector<'db> {
def_id.kind
{
self.deferred_builtin_derives
.entry(ast_id.ast_id.upcast())
.entry(ast_id_without_path.upcast())
.or_default()
.push(DeferredBuiltinDerive {
call_id,
@ -1541,24 +1563,15 @@ impl<'db> DefCollector<'db> {
depth: directive.depth,
derive_attr_id: *attr_id,
derive_index: idx as u32,
helpers_range: helpers_start..helpers_end,
});
} else {
push_resolved(&mut resolved, directive, call_id);
push_resolved(&mut resolved, &directive, call_id);
*derive_call_ids.last_mut().unwrap() =
Some(Either::Left(call_id));
}
} else {
derive_call_ids.push(None);
self.unresolved_macros.push(MacroDirective {
module_id: directive.module_id,
depth: directive.depth + 1,
kind: MacroDirectiveKind::Derive {
ast_id,
derive_attr: *attr_id,
derive_pos: idx,
ctxt: call_site.ctx,
derive_macro_id: call_id,
},
container: directive.container,
});
self.unresolved_macros.push(directive);
}
}
@ -1858,9 +1871,8 @@ impl ModCollector<'_, '_> {
ast_id: FileAstId<ast::Adt>,
id: AdtId,
def_map: &mut DefMap| {
let Some(deferred_derives) =
deferred_derives.remove(&InFile::new(file_id, ast_id.upcast()))
else {
let ast_id = InFile::new(file_id, ast_id.upcast());
let Some(deferred_derives) = deferred_derives.remove(&ast_id.upcast()) else {
return;
};
let module = &mut def_map.modules[module_id];
@ -1876,6 +1888,22 @@ impl ModCollector<'_, '_> {
},
);
module.scope.define_builtin_derive_impl(impl_id);
module.scope.set_derive_macro_invoc(
ast_id,
Either::Right(impl_id),
deferred_derive.derive_attr_id,
deferred_derive.derive_index as usize,
);
// Change its helper attributes to the new id.
if let Some(derive_helpers) =
def_map.derive_helpers_in_scope.get_mut(&ast_id.map(|it| it.upcast()))
{
for (_, _, call_id) in
&mut derive_helpers[deferred_derive.helpers_range.clone()]
{
*call_id = Either::Right(impl_id);
}
}
});
}
};

View file

@ -13,7 +13,7 @@ use std::{
use base_db::FxIndexSet;
use either::Either;
use hir_def::{
DefWithBodyId, MacroId, StructId, TraitId, VariantId,
BuiltinDeriveImplId, DefWithBodyId, HasModule, MacroId, StructId, TraitId, VariantId,
attrs::parse_extra_crate_attrs,
expr_store::{Body, ExprOrPatSource, HygieneId, path::Path},
hir::{BindingId, Expr, ExprId, ExprOrPatId, Pat},
@ -622,7 +622,20 @@ impl<'db> SemanticsImpl<'db> {
Some(
calls
.into_iter()
.map(|call| macro_call_to_macro_id(ctx, call?).map(|id| Macro { id }))
.map(|call| {
let call = call?;
match call {
Either::Left(call) => {
macro_call_to_macro_id(ctx, call).map(|id| Macro { id })
}
Either::Right(call) => {
let call = call.loc(self.db);
let krate = call.krate(self.db);
let lang_items = hir_def::lang_item::lang_items(self.db, krate);
call.trait_.derive_macro(lang_items).map(|id| Macro { id })
}
}
})
.collect(),
)
})
@ -633,7 +646,7 @@ impl<'db> SemanticsImpl<'db> {
.derive_macro_calls(attr)?
.into_iter()
.flat_map(|call| {
let file_id = call?;
let file_id = call?.left()?;
let ExpandResult { value, err } = self.db.parse_macro_expansion(file_id);
let root_node = value.0.syntax_node();
self.cache(root_node.clone(), file_id.into());
@ -643,7 +656,10 @@ impl<'db> SemanticsImpl<'db> {
Some(res)
}
fn derive_macro_calls(&self, attr: &ast::Attr) -> Option<Vec<Option<MacroCallId>>> {
fn derive_macro_calls(
&self,
attr: &ast::Attr,
) -> Option<Vec<Option<Either<MacroCallId, BuiltinDeriveImplId>>>> {
let adt = attr.syntax().parent().and_then(ast::Adt::cast)?;
let file_id = self.find_file(adt.syntax()).file_id;
let adt = InFile::new(file_id, &adt);
@ -690,8 +706,9 @@ impl<'db> SemanticsImpl<'db> {
.derive_helpers_in_scope(InFile::new(sa.file_id, id))?
.iter()
.filter(|&(name, _, _)| *name == attr_name)
.map(|&(_, macro_, call)| (macro_.into(), call))
.filter_map(|&(_, macro_, call)| Some((macro_.into(), call.left()?)))
.collect();
// FIXME: We filter our builtin derive "fake" expansions, is this correct? Should we still expose them somehow?
res.is_empty().not().then_some(res)
}
@ -1338,6 +1355,7 @@ impl<'db> SemanticsImpl<'db> {
// FIXME: We need to call `f` for all of them as well though!
process_expansion_for_token(ctx, &mut stack, derive_attr);
for derive in derives.into_iter().flatten() {
let Either::Left(derive) = derive else { continue };
process_expansion_for_token(ctx, &mut stack, derive);
}
}
@ -1467,11 +1485,12 @@ impl<'db> SemanticsImpl<'db> {
for (.., derive) in
helpers.iter().filter(|(helper, ..)| *helper == attr_name)
{
let Either::Left(derive) = *derive else { continue };
// as there may be multiple derives registering the same helper
// name, we gotta make sure to call this for all of them!
// FIXME: We need to call `f` for all of them as well though!
res = res
.or(process_expansion_for_token(ctx, &mut stack, *derive));
.or(process_expansion_for_token(ctx, &mut stack, derive));
}
res
})

View file

@ -87,10 +87,10 @@
use either::Either;
use hir_def::{
AdtId, BlockId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternBlockId,
ExternCrateId, FieldId, FunctionId, GenericDefId, GenericParamId, ImplId, LifetimeParamId,
Lookup, MacroId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, TypeParamId, UnionId,
UseId, VariantId,
AdtId, BlockId, BuiltinDeriveImplId, ConstId, ConstParamId, DefWithBodyId, EnumId,
EnumVariantId, ExternBlockId, ExternCrateId, FieldId, FunctionId, GenericDefId, GenericParamId,
ImplId, LifetimeParamId, Lookup, MacroId, ModuleId, StaticId, StructId, TraitId, TypeAliasId,
TypeParamId, UnionId, UseId, VariantId,
dyn_map::{
DynMap,
keys::{self, Key},
@ -394,7 +394,7 @@ impl SourceToDefCtx<'_, '_> {
&mut self,
item: InFile<&ast::Adt>,
src: InFile<ast::Attr>,
) -> Option<(AttrId, MacroCallId, &[Option<MacroCallId>])> {
) -> Option<(AttrId, MacroCallId, &[Option<Either<MacroCallId, BuiltinDeriveImplId>>])> {
let map = self.dyn_map(item)?;
map[keys::DERIVE_MACRO_CALL]
.get(&AstPtr::new(&src.value))
@ -409,8 +409,11 @@ impl SourceToDefCtx<'_, '_> {
pub(super) fn derive_macro_calls<'slf>(
&'slf mut self,
adt: InFile<&ast::Adt>,
) -> Option<impl Iterator<Item = (AttrId, MacroCallId, &'slf [Option<MacroCallId>])> + use<'slf>>
{
) -> Option<
impl Iterator<
Item = (AttrId, MacroCallId, &'slf [Option<Either<MacroCallId, BuiltinDeriveImplId>>]),
> + use<'slf>,
> {
self.dyn_map(adt).as_ref().map(|&map| {
let dyn_map = &map[keys::DERIVE_MACRO_CALL];
adt.value

View file

@ -583,26 +583,16 @@ fn main() {
fn macro_expand_derive() {
check(
r#"
//- proc_macros: identity
//- minicore: clone, derive
//- proc_macros: identity, derive_identity
//- minicore: derive
#[proc_macros::identity]
#[derive(C$0lone)]
#[derive(proc_macros::DeriveIde$0ntity)]
struct Foo {}
"#,
expect![[r#"
Clone
impl <>core::clone::Clone for Foo< >where {
fn clone(&self) -> Self {
match self {
Foo{}
=> Foo{}
,
}
}
}"#]],
proc_macros::DeriveIdentity
struct Foo{}"#]],
);
}
@ -610,15 +600,17 @@ struct Foo {}
fn macro_expand_derive2() {
check(
r#"
//- minicore: copy, clone, derive
//- proc_macros: derive_identity
//- minicore: derive
#[derive(Cop$0y)]
#[derive(Clone)]
#[derive(proc_macros::$0DeriveIdentity)]
#[derive(proc_macros::DeriveIdentity)]
struct Foo {}
"#,
expect![[r#"
Copy
impl <>core::marker::Copy for Foo< >where{}"#]],
proc_macros::DeriveIdentity
#[derive(proc_macros::DeriveIdentity)]
struct Foo{}"#]],
);
}
@ -626,35 +618,27 @@ struct Foo {}
fn macro_expand_derive_multi() {
check(
r#"
//- minicore: copy, clone, derive
//- proc_macros: derive_identity
//- minicore: derive
#[derive(Cop$0y, Clone)]
#[derive(proc_macros::DeriveIdent$0ity, proc_macros::DeriveIdentity)]
struct Foo {}
"#,
expect![[r#"
Copy
impl <>core::marker::Copy for Foo< >where{}"#]],
proc_macros::DeriveIdentity
struct Foo{}"#]],
);
check(
r#"
//- minicore: copy, clone, derive
//- proc_macros: derive_identity
//- minicore: derive
#[derive(Copy, Cl$0one)]
#[derive(proc_macros::DeriveIdentity, proc_macros::De$0riveIdentity)]
struct Foo {}
"#,
expect![[r#"
Clone
impl <>core::clone::Clone for Foo< >where {
fn clone(&self) -> Self {
match self {
Foo{}
=> Foo{}
,
}
}
}"#]],
proc_macros::DeriveIdentity
struct Foo{}"#]],
);
}

View file

@ -532,4 +532,5 @@ define_symbols! {
CoerceUnsized,
DispatchFromDyn,
define_opaque,
marker,
}