Skip to content

Commit 9778068

Browse files
committed
Auto merge of rust-lang#79078 - petrochenkov:derattr, r=Aaron1011
expand/resolve: Turn `#[derive]` into a regular macro attribute This PR turns `#[derive]` into a regular attribute macro declared in libcore and defined in `rustc_builtin_macros`, like it was previously done with other "active" attributes in rust-lang#62086, rust-lang#62735 and other PRs. This PR is also a continuation of rust-lang#65252, rust-lang#69870 and other PRs linked from them, which layed the ground for converting `#[derive]` specifically. `#[derive]` still asks `rustc_resolve` to resolve paths inside `derive(...)`, and `rustc_expand` gets those resolution results through some backdoor (which I'll try to address later), but otherwise `#[derive]` is treated as any other macro attributes, which simplifies the resolution-expansion infra pretty significantly. The change has several observable effects on language and library. Some of the language changes are **feature-gated** by [`feature(macro_attributes_in_derive_output)`](rust-lang#81119). #### Library - `derive` is now available through standard library as `{core,std}::prelude::v1::derive`. #### Language - `derive` now goes through name resolution, so it can now be renamed - `use derive as my_derive; #[my_derive(Debug)] struct S;`. - `derive` now goes through name resolution, so this resolution can fail in corner cases. Crater found one such regression, where import `use foo as derive` goes into a cycle with `#[derive(Something)]`. - **[feature-gated]** `#[derive]` is now expanded as any other attributes in left-to-right order. This allows to remove the restriction on other macro attributes following `#[derive]` (rust-lang/reference#566). The following macro attributes become a part of the derive's input (this is not a change, non-macro attributes following `#[derive]` were treated in the same way previously). - `#[derive]` is now expanded as any other attributes in left-to-right order. This means two derive attributes `#[derive(Foo)] #[derive(Bar)]` are now expanded separately rather than together. It doesn't generally make difference, except for esoteric cases. For example `#[derive(Foo)]` can now produce an import bringing `Bar` into scope, but previously both `Foo` and `Bar` were required to be resolved before expanding any of them. - **[feature-gated]** `#[derive()]` (with empty list in parentheses) actually becomes useful. For historical reasons `#[derive]` *fully configures* its input, eagerly evaluating `cfg` everywhere in its target, for example on fields. Expansion infra doesn't do that for other attributes, but now when macro attributes attributes are allowed to be written after `#[derive]`, it means that derive can *fully configure* items for them. ```rust #[derive()] #[my_attr] struct S { #[cfg(FALSE)] // this field in removed by `#[derive()]` and not observed by `#[my_attr]` field: u8 } ``` - `#[derive]` on some non-item targets is now prohibited. This was accidentally allowed as noop in the past, but was warned about since early 2018 (rust-lang#50092), despite that crater found a few such cases in unmaintained crates. - Derive helper attributes used before their introduction are now reported with a deprecation lint. This change is long overdue (since macro modularization, rust-lang#52226 (comment)), but it was hard to do without fixing expansion order for derives. The deprecation is tracked by rust-lang#79202. ```rust #[trait_helper] // warning: derive helper attribute is used before it is introduced #[derive(Trait)] struct S {} ``` Crater analysis: rust-lang#79078 (comment)
2 parents 36ecbc9 + d8af6de commit 9778068

File tree

65 files changed

+1396
-1008
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1396
-1008
lines changed

Diff for: Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -3582,6 +3582,7 @@ dependencies = [
35823582
"rustc_errors",
35833583
"rustc_expand",
35843584
"rustc_feature",
3585+
"rustc_lexer",
35853586
"rustc_parse",
35863587
"rustc_parse_format",
35873588
"rustc_session",

Diff for: compiler/rustc_builtin_macros/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ rustc_attr = { path = "../rustc_attr" }
1515
rustc_data_structures = { path = "../rustc_data_structures" }
1616
rustc_errors = { path = "../rustc_errors" }
1717
rustc_feature = { path = "../rustc_feature" }
18+
rustc_lexer = { path = "../rustc_lexer" }
1819
rustc_parse = { path = "../rustc_parse" }
1920
rustc_target = { path = "../rustc_target" }
2021
rustc_session = { path = "../rustc_session" }

Diff for: compiler/rustc_builtin_macros/src/derive.rs

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
use rustc_ast::{self as ast, token, ItemKind, MetaItemKind, NestedMetaItem, StmtKind};
2+
use rustc_errors::{struct_span_err, Applicability};
3+
use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier};
4+
use rustc_expand::config::StripUnconfigured;
5+
use rustc_feature::AttributeTemplate;
6+
use rustc_parse::validate_attr;
7+
use rustc_session::Session;
8+
use rustc_span::symbol::sym;
9+
use rustc_span::Span;
10+
11+
crate struct Expander;
12+
13+
impl MultiItemModifier for Expander {
14+
fn expand(
15+
&self,
16+
ecx: &mut ExtCtxt<'_>,
17+
span: Span,
18+
meta_item: &ast::MetaItem,
19+
item: Annotatable,
20+
) -> ExpandResult<Vec<Annotatable>, Annotatable> {
21+
let sess = ecx.sess;
22+
if report_bad_target(sess, &item, span) {
23+
// We don't want to pass inappropriate targets to derive macros to avoid
24+
// follow up errors, all other errors below are recoverable.
25+
return ExpandResult::Ready(vec![item]);
26+
}
27+
28+
let template =
29+
AttributeTemplate { list: Some("Trait1, Trait2, ..."), ..Default::default() };
30+
let attr = ecx.attribute(meta_item.clone());
31+
validate_attr::check_builtin_attribute(&sess.parse_sess, &attr, sym::derive, template);
32+
33+
let derives: Vec<_> = attr
34+
.meta_item_list()
35+
.unwrap_or_default()
36+
.into_iter()
37+
.filter_map(|nested_meta| match nested_meta {
38+
NestedMetaItem::MetaItem(meta) => Some(meta),
39+
NestedMetaItem::Literal(lit) => {
40+
// Reject `#[derive("Debug")]`.
41+
report_unexpected_literal(sess, &lit);
42+
None
43+
}
44+
})
45+
.map(|meta| {
46+
// Reject `#[derive(Debug = "value", Debug(abc))]`, but recover the paths.
47+
report_path_args(sess, &meta);
48+
meta.path
49+
})
50+
.collect();
51+
52+
// FIXME: Try to cache intermediate results to avoid collecting same paths multiple times.
53+
match ecx.resolver.resolve_derives(ecx.current_expansion.id, derives, ecx.force_mode) {
54+
Ok(()) => {
55+
let mut visitor =
56+
StripUnconfigured { sess, features: ecx.ecfg.features, modified: false };
57+
let mut item = visitor.fully_configure(item);
58+
if visitor.modified {
59+
// Erase the tokens if cfg-stripping modified the item
60+
// This will cause us to synthesize fake tokens
61+
// when `nt_to_tokenstream` is called on this item.
62+
match &mut item {
63+
Annotatable::Item(item) => item,
64+
Annotatable::Stmt(stmt) => match &mut stmt.kind {
65+
StmtKind::Item(item) => item,
66+
_ => unreachable!(),
67+
},
68+
_ => unreachable!(),
69+
}
70+
.tokens = None;
71+
}
72+
ExpandResult::Ready(vec![item])
73+
}
74+
Err(Indeterminate) => ExpandResult::Retry(item),
75+
}
76+
}
77+
}
78+
79+
fn report_bad_target(sess: &Session, item: &Annotatable, span: Span) -> bool {
80+
let item_kind = match item {
81+
Annotatable::Item(item) => Some(&item.kind),
82+
Annotatable::Stmt(stmt) => match &stmt.kind {
83+
StmtKind::Item(item) => Some(&item.kind),
84+
_ => None,
85+
},
86+
_ => None,
87+
};
88+
89+
let bad_target =
90+
!matches!(item_kind, Some(ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..)));
91+
if bad_target {
92+
struct_span_err!(
93+
sess,
94+
span,
95+
E0774,
96+
"`derive` may only be applied to structs, enums and unions",
97+
)
98+
.emit();
99+
}
100+
bad_target
101+
}
102+
103+
fn report_unexpected_literal(sess: &Session, lit: &ast::Lit) {
104+
let help_msg = match lit.token.kind {
105+
token::Str if rustc_lexer::is_ident(&lit.token.symbol.as_str()) => {
106+
format!("try using `#[derive({})]`", lit.token.symbol)
107+
}
108+
_ => "for example, write `#[derive(Debug)]` for `Debug`".to_string(),
109+
};
110+
struct_span_err!(sess, lit.span, E0777, "expected path to a trait, found literal",)
111+
.help(&help_msg)
112+
.emit();
113+
}
114+
115+
fn report_path_args(sess: &Session, meta: &ast::MetaItem) {
116+
let report_error = |title, action| {
117+
let span = meta.span.with_lo(meta.path.span.hi());
118+
sess.struct_span_err(span, title)
119+
.span_suggestion(span, action, String::new(), Applicability::MachineApplicable)
120+
.emit();
121+
};
122+
match meta.kind {
123+
MetaItemKind::Word => {}
124+
MetaItemKind::List(..) => report_error(
125+
"traits in `#[derive(...)]` don't accept arguments",
126+
"remove the arguments",
127+
),
128+
MetaItemKind::NameValue(..) => {
129+
report_error("traits in `#[derive(...)]` don't accept values", "remove the value")
130+
}
131+
}
132+
}

Diff for: compiler/rustc_builtin_macros/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ mod cfg_accessible;
2727
mod compile_error;
2828
mod concat;
2929
mod concat_idents;
30+
mod derive;
3031
mod deriving;
3132
mod env;
3233
mod format;
@@ -88,6 +89,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
8889
register_attr! {
8990
bench: test::expand_bench,
9091
cfg_accessible: cfg_accessible::Expander,
92+
derive: derive::Expander,
9193
global_allocator: global_allocator::expand,
9294
test: test::expand_test,
9395
test_case: test::expand_test_case,

Diff for: compiler/rustc_expand/src/base.rs

+19-28
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,10 @@ impl Annotatable {
141141
}
142142

143143
crate fn into_tokens(self, sess: &ParseSess) -> TokenStream {
144-
nt_to_tokenstream(&self.into_nonterminal(), sess, CanSynthesizeMissingTokens::No)
144+
// Tokens of an attribute target may be invalidated by some outer `#[derive]` performing
145+
// "full configuration" (attributes following derives on the same item should be the most
146+
// common case), that's why synthesizing tokens is allowed.
147+
nt_to_tokenstream(&self.into_nonterminal(), sess, CanSynthesizeMissingTokens::Yes)
145148
}
146149

147150
pub fn expect_item(self) -> P<ast::Item> {
@@ -234,25 +237,6 @@ impl Annotatable {
234237
_ => panic!("expected variant"),
235238
}
236239
}
237-
238-
pub fn derive_allowed(&self) -> bool {
239-
match *self {
240-
Annotatable::Stmt(ref stmt) => match stmt.kind {
241-
ast::StmtKind::Item(ref item) => matches!(
242-
item.kind,
243-
ast::ItemKind::Struct(..) | ast::ItemKind::Enum(..) | ast::ItemKind::Union(..)
244-
),
245-
_ => false,
246-
},
247-
Annotatable::Item(ref item) => match item.kind {
248-
ast::ItemKind::Struct(..) | ast::ItemKind::Enum(..) | ast::ItemKind::Union(..) => {
249-
true
250-
}
251-
_ => false,
252-
},
253-
_ => false,
254-
}
255-
}
256240
}
257241

258242
/// Result of an expansion that may need to be retried.
@@ -854,12 +838,6 @@ impl SyntaxExtension {
854838
}
855839
}
856840

857-
/// Result of resolving a macro invocation.
858-
pub enum InvocationRes {
859-
Single(Lrc<SyntaxExtension>),
860-
DeriveContainer(Vec<Lrc<SyntaxExtension>>),
861-
}
862-
863841
/// Error type that denotes indeterminacy.
864842
pub struct Indeterminate;
865843

@@ -885,16 +863,29 @@ pub trait ResolverExpand {
885863
invoc: &Invocation,
886864
eager_expansion_root: ExpnId,
887865
force: bool,
888-
) -> Result<InvocationRes, Indeterminate>;
866+
) -> Result<Lrc<SyntaxExtension>, Indeterminate>;
889867

890868
fn check_unused_macros(&mut self);
891869

892870
/// Some parent node that is close enough to the given macro call.
893-
fn lint_node_id(&mut self, expn_id: ExpnId) -> NodeId;
871+
fn lint_node_id(&self, expn_id: ExpnId) -> NodeId;
894872

895873
// Resolver interfaces for specific built-in macros.
896874
/// Does `#[derive(...)]` attribute with the given `ExpnId` have built-in `Copy` inside it?
897875
fn has_derive_copy(&self, expn_id: ExpnId) -> bool;
876+
/// Resolve paths inside the `#[derive(...)]` attribute with the given `ExpnId`.
877+
fn resolve_derives(
878+
&mut self,
879+
expn_id: ExpnId,
880+
derives: Vec<ast::Path>,
881+
force: bool,
882+
) -> Result<(), Indeterminate>;
883+
/// Take resolutions for paths inside the `#[derive(...)]` attribute with the given `ExpnId`
884+
/// back from resolver.
885+
fn take_derive_resolutions(
886+
&mut self,
887+
expn_id: ExpnId,
888+
) -> Option<Vec<(Lrc<SyntaxExtension>, ast::Path)>>;
898889
/// Path resolution logic for `#[cfg_accessible(path)]`.
899890
fn cfg_accessible(&mut self, expn_id: ExpnId, path: &ast::Path) -> Result<bool, Indeterminate>;
900891
}

0 commit comments

Comments
 (0)