1
Fork 0

expand/resolve: Turn #[derive] into a regular macro attribute

This commit is contained in:
Vadim Petrochenkov 2020-11-14 14:47:14 +03:00
parent ae00b62ceb
commit dbdbd30bf2
58 changed files with 1499 additions and 1258 deletions

View file

@ -15,6 +15,7 @@ rustc_attr = { path = "../rustc_attr" }
rustc_data_structures = { path = "../rustc_data_structures" }
rustc_errors = { path = "../rustc_errors" }
rustc_feature = { path = "../rustc_feature" }
rustc_lexer = { path = "../rustc_lexer" }
rustc_parse = { path = "../rustc_parse" }
rustc_target = { path = "../rustc_target" }
rustc_session = { path = "../rustc_session" }

View file

@ -0,0 +1,132 @@
use rustc_ast::{self as ast, token, ItemKind, MetaItemKind, NestedMetaItem, StmtKind};
use rustc_errors::{struct_span_err, Applicability};
use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier};
use rustc_expand::config::StripUnconfigured;
use rustc_feature::AttributeTemplate;
use rustc_parse::validate_attr;
use rustc_session::Session;
use rustc_span::symbol::sym;
use rustc_span::Span;
crate struct Expander;
impl MultiItemModifier for Expander {
fn expand(
&self,
ecx: &mut ExtCtxt<'_>,
span: Span,
meta_item: &ast::MetaItem,
item: Annotatable,
) -> ExpandResult<Vec<Annotatable>, Annotatable> {
let sess = ecx.sess;
if report_bad_target(sess, &item, span) {
// We don't want to pass inappropriate targets to derive macros to avoid
// follow up errors, all other errors below are recoverable.
return ExpandResult::Ready(vec![item]);
}
let template =
AttributeTemplate { list: Some("Trait1, Trait2, ..."), ..Default::default() };
let attr = ecx.attribute(meta_item.clone());
validate_attr::check_builtin_attribute(&sess.parse_sess, &attr, sym::derive, template);
let derives: Vec<_> = attr
.meta_item_list()
.unwrap_or_default()
.into_iter()
.filter_map(|nested_meta| match nested_meta {
NestedMetaItem::MetaItem(meta) => Some(meta),
NestedMetaItem::Literal(lit) => {
// Reject `#[derive("Debug")]`.
report_unexpected_literal(sess, &lit);
None
}
})
.map(|meta| {
// Reject `#[derive(Debug = "value", Debug(abc))]`, but recover the paths.
report_path_args(sess, &meta);
meta.path
})
.collect();
// FIXME: Try to cache intermediate results to avoid collecting same paths multiple times.
match ecx.resolver.resolve_derives(ecx.current_expansion.id, derives, ecx.force_mode) {
Ok(()) => {
let mut visitor =
StripUnconfigured { sess, features: ecx.ecfg.features, modified: false };
let mut item = visitor.fully_configure(item);
if visitor.modified {
// Erase the tokens if cfg-stripping modified the item
// This will cause us to synthesize fake tokens
// when `nt_to_tokenstream` is called on this item.
match &mut item {
Annotatable::Item(item) => item,
Annotatable::Stmt(stmt) => match &mut stmt.kind {
StmtKind::Item(item) => item,
_ => unreachable!(),
},
_ => unreachable!(),
}
.tokens = None;
}
ExpandResult::Ready(vec![item])
}
Err(Indeterminate) => ExpandResult::Retry(item),
}
}
}
fn report_bad_target(sess: &Session, item: &Annotatable, span: Span) -> bool {
let item_kind = match item {
Annotatable::Item(item) => Some(&item.kind),
Annotatable::Stmt(stmt) => match &stmt.kind {
StmtKind::Item(item) => Some(&item.kind),
_ => None,
},
_ => None,
};
let bad_target =
!matches!(item_kind, Some(ItemKind::Struct(..) | ItemKind::Enum(..) | ItemKind::Union(..)));
if bad_target {
struct_span_err!(
sess,
span,
E0774,
"`derive` may only be applied to structs, enums and unions",
)
.emit();
}
bad_target
}
fn report_unexpected_literal(sess: &Session, lit: &ast::Lit) {
let help_msg = match lit.token.kind {
token::Str if rustc_lexer::is_ident(&lit.token.symbol.as_str()) => {
format!("try using `#[derive({})]`", lit.token.symbol)
}
_ => "for example, write `#[derive(Debug)]` for `Debug`".to_string(),
};
struct_span_err!(sess, lit.span, E0777, "expected path to a trait, found literal",)
.help(&help_msg)
.emit();
}
fn report_path_args(sess: &Session, meta: &ast::MetaItem) {
let report_error = |title, action| {
let span = meta.span.with_lo(meta.path.span.hi());
sess.struct_span_err(span, title)
.span_suggestion(span, action, String::new(), Applicability::MachineApplicable)
.emit();
};
match meta.kind {
MetaItemKind::Word => {}
MetaItemKind::List(..) => report_error(
"traits in `#[derive(...)]` don't accept arguments",
"remove the arguments",
),
MetaItemKind::NameValue(..) => {
report_error("traits in `#[derive(...)]` don't accept values", "remove the value")
}
}
}

View file

@ -27,6 +27,7 @@ mod cfg_accessible;
mod compile_error;
mod concat;
mod concat_idents;
mod derive;
mod deriving;
mod env;
mod format;
@ -88,6 +89,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
register_attr! {
bench: test::expand_bench,
cfg_accessible: cfg_accessible::Expander,
derive: derive::Expander,
global_allocator: global_allocator::expand,
test: test::expand_test,
test_case: test::expand_test_case,

View file

@ -141,7 +141,7 @@ impl Annotatable {
}
crate fn into_tokens(self, sess: &ParseSess) -> TokenStream {
nt_to_tokenstream(&self.into_nonterminal(), sess, CanSynthesizeMissingTokens::No)
nt_to_tokenstream(&self.into_nonterminal(), sess, CanSynthesizeMissingTokens::Yes)
}
pub fn expect_item(self) -> P<ast::Item> {
@ -234,25 +234,6 @@ impl Annotatable {
_ => panic!("expected variant"),
}
}
pub fn derive_allowed(&self) -> bool {
match *self {
Annotatable::Stmt(ref stmt) => match stmt.kind {
ast::StmtKind::Item(ref item) => matches!(
item.kind,
ast::ItemKind::Struct(..) | ast::ItemKind::Enum(..) | ast::ItemKind::Union(..)
),
_ => false,
},
Annotatable::Item(ref item) => match item.kind {
ast::ItemKind::Struct(..) | ast::ItemKind::Enum(..) | ast::ItemKind::Union(..) => {
true
}
_ => false,
},
_ => false,
}
}
}
/// Result of an expansion that may need to be retried.
@ -854,12 +835,6 @@ impl SyntaxExtension {
}
}
/// Result of resolving a macro invocation.
pub enum InvocationRes {
Single(Lrc<SyntaxExtension>),
DeriveContainer(Vec<Lrc<SyntaxExtension>>),
}
/// Error type that denotes indeterminacy.
pub struct Indeterminate;
@ -885,16 +860,29 @@ pub trait ResolverExpand {
invoc: &Invocation,
eager_expansion_root: ExpnId,
force: bool,
) -> Result<InvocationRes, Indeterminate>;
) -> Result<Lrc<SyntaxExtension>, Indeterminate>;
fn check_unused_macros(&mut self);
/// Some parent node that is close enough to the given macro call.
fn lint_node_id(&mut self, expn_id: ExpnId) -> NodeId;
fn lint_node_id(&self, expn_id: ExpnId) -> NodeId;
// Resolver interfaces for specific built-in macros.
/// Does `#[derive(...)]` attribute with the given `ExpnId` have built-in `Copy` inside it?
fn has_derive_copy(&self, expn_id: ExpnId) -> bool;
/// Resolve paths inside the `#[derive(...)]` attribute with the given `ExpnId`.
fn resolve_derives(
&mut self,
expn_id: ExpnId,
derives: Vec<ast::Path>,
force: bool,
) -> Result<(), Indeterminate>;
/// Take resolutions for paths inside the `#[derive(...)]` attribute with the given `ExpnId`
/// back from resolver.
fn take_derive_resolutions(
&mut self,
expn_id: ExpnId,
) -> Option<Vec<(Lrc<SyntaxExtension>, ast::Path)>>;
/// Path resolution logic for `#[cfg_accessible(path)]`.
fn cfg_accessible(&mut self, expn_id: ExpnId, path: &ast::Path) -> Result<bool, Indeterminate>;
}

View file

@ -1,24 +1,26 @@
use crate::base::*;
use crate::config::StripUnconfigured;
use crate::configure;
use crate::hygiene::{ExpnData, ExpnKind, SyntaxContext};
use crate::hygiene::SyntaxContext;
use crate::mbe::macro_rules::annotate_err_with_kind;
use crate::module::{parse_external_mod, push_directory, Directory, DirectoryOwnership};
use crate::placeholders::{placeholder, PlaceholderExpander};
use crate::proc_macro::collect_derives;
use rustc_ast as ast;
use rustc_ast::mut_visit::*;
use rustc_ast::ptr::P;
use rustc_ast::token;
use rustc_ast::tokenstream::TokenStream;
use rustc_ast::visit::{self, AssocCtxt, Visitor};
use rustc_ast::{self as ast, AttrItem, AttrStyle, Block, LitKind, NodeId, PatKind, Path};
use rustc_ast::{ItemKind, MacArgs, MacCallStmt, MacStmtStyle, StmtKind, Unsafe};
use rustc_ast::{AttrItem, AttrStyle, Block, ItemKind, LitKind, MacArgs};
use rustc_ast::{MacCallStmt, MacStmtStyle, MetaItemKind, NestedMetaItem};
use rustc_ast::{NodeId, PatKind, Path, StmtKind, Unsafe};
use rustc_ast_pretty::pprust;
use rustc_attr::{self as attr, is_builtin_attr, HasAttrs};
use rustc_data_structures::map_in_place::MapInPlace;
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_errors::{struct_span_err, Applicability, PResult};
use rustc_data_structures::sync::Lrc;
use rustc_errors::{Applicability, PResult};
use rustc_feature::Features;
use rustc_parse::parser::{AttemptLocalParseRecovery, ForceCollect, Parser};
use rustc_parse::validate_attr;
@ -302,20 +304,11 @@ pub enum InvocationKind {
item: Annotatable,
// Required for resolving derive helper attributes.
derives: Vec<Path>,
// We temporarily report errors for attribute macros placed after derives
after_derive: bool,
},
Derive {
path: Path,
item: Annotatable,
},
/// "Invocation" that contains all derives from an item,
/// broken into multiple `Derive` invocations when expanded.
/// FIXME: Find a way to remove it.
DeriveContainer {
derives: Vec<Path>,
item: Annotatable,
},
}
impl InvocationKind {
@ -328,7 +321,6 @@ impl InvocationKind {
match self {
InvocationKind::Attr { item: Annotatable::StructField(field), .. }
| InvocationKind::Derive { item: Annotatable::StructField(field), .. }
| InvocationKind::DeriveContainer { item: Annotatable::StructField(field), .. }
if field.ident.is_none() =>
{
Some(field.vis.clone())
@ -344,7 +336,6 @@ impl Invocation {
InvocationKind::Bang { span, .. } => *span,
InvocationKind::Attr { attr, .. } => attr.span,
InvocationKind::Derive { path, .. } => path.span,
InvocationKind::DeriveContainer { item, .. } => item.span(),
}
}
}
@ -446,7 +437,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
let mut undetermined_invocations = Vec::new();
let (mut progress, mut force) = (false, !self.monotonic);
loop {
let (invoc, res) = if let Some(invoc) = invocations.pop() {
let (invoc, ext) = if let Some(invoc) = invocations.pop() {
invoc
} else {
self.resolve_imports();
@ -464,8 +455,8 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
continue;
};
let res = match res {
Some(res) => res,
let ext = match ext {
Some(ext) => ext,
None => {
let eager_expansion_root = if self.monotonic {
invoc.expansion_data.id
@ -477,7 +468,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
eager_expansion_root,
force,
) {
Ok(res) => res,
Ok(ext) => ext,
Err(Indeterminate) => {
// Cannot resolve, will retry this invocation later.
undetermined_invocations.push((invoc, None));
@ -491,86 +482,78 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
self.cx.current_expansion = invoc.expansion_data.clone();
self.cx.force_mode = force;
// FIXME(jseyfried): Refactor out the following logic
let fragment_kind = invoc.fragment_kind;
let (expanded_fragment, new_invocations) = match res {
InvocationRes::Single(ext) => match self.expand_invoc(invoc, &ext.kind) {
ExpandResult::Ready(fragment) => self.collect_invocations(fragment, &[]),
ExpandResult::Retry(invoc) => {
if force {
self.cx.span_bug(
invoc.span(),
"expansion entered force mode but is still stuck",
);
} else {
// Cannot expand, will retry this invocation later.
undetermined_invocations
.push((invoc, Some(InvocationRes::Single(ext))));
continue;
}
}
},
InvocationRes::DeriveContainer(_exts) => {
// FIXME: Consider using the derive resolutions (`_exts`) immediately,
// instead of enqueuing the derives to be resolved again later.
let (derives, mut item) = match invoc.kind {
InvocationKind::DeriveContainer { derives, item } => (derives, item),
_ => unreachable!(),
};
let (item, derive_placeholders) = if !item.derive_allowed() {
self.error_derive_forbidden_on_non_adt(&derives, &item);
item.visit_attrs(|attrs| attrs.retain(|a| !a.has_name(sym::derive)));
(item, Vec::new())
} else {
let mut visitor = StripUnconfigured {
sess: self.cx.sess,
features: self.cx.ecfg.features,
modified: false,
};
let mut item = visitor.fully_configure(item);
item.visit_attrs(|attrs| attrs.retain(|a| !a.has_name(sym::derive)));
if visitor.modified && !derives.is_empty() {
// Erase the tokens if cfg-stripping modified the item
// This will cause us to synthesize fake tokens
// when `nt_to_tokenstream` is called on this item.
match &mut item {
Annotatable::Item(item) => item.tokens = None,
Annotatable::Stmt(stmt) => {
if let StmtKind::Item(item) = &mut stmt.kind {
item.tokens = None
} else {
panic!("Unexpected stmt {:?}", stmt);
}
}
_ => panic!("Unexpected annotatable {:?}", item),
let (expanded_fragment, new_invocations) = match self.expand_invoc(invoc, &ext.kind) {
ExpandResult::Ready(fragment) => {
let derive_placeholders = self
.cx
.resolver
.take_derive_resolutions(expn_id)
.map(|derives| {
enum AnnotatableRef<'a> {
Item(&'a P<ast::Item>),
Stmt(&'a ast::Stmt),
}
}
let item = match &fragment {
AstFragment::Items(items) => match &items[..] {
[item] => AnnotatableRef::Item(item),
_ => unreachable!(),
},
AstFragment::Stmts(stmts) => match &stmts[..] {
[stmt] => AnnotatableRef::Stmt(stmt),
_ => unreachable!(),
},
_ => unreachable!(),
};
invocations.reserve(derives.len());
let derive_placeholders = derives
.into_iter()
.map(|path| {
let expn_id = ExpnId::fresh(None);
invocations.push((
Invocation {
kind: InvocationKind::Derive { path, item: item.clone() },
fragment_kind,
expansion_data: ExpansionData {
id: expn_id,
..self.cx.current_expansion.clone()
invocations.reserve(derives.len());
derives
.into_iter()
.map(|(_exts, path)| {
// FIXME: Consider using the derive resolutions (`_exts`)
// instead of enqueuing the derives to be resolved again later.
let expn_id = ExpnId::fresh(None);
invocations.push((
Invocation {
kind: InvocationKind::Derive {
path,
item: match item {
AnnotatableRef::Item(item) => {
Annotatable::Item(item.clone())
}
AnnotatableRef::Stmt(stmt) => {
Annotatable::Stmt(P(stmt.clone()))
}
},
},
fragment_kind,
expansion_data: ExpansionData {
id: expn_id,
..self.cx.current_expansion.clone()
},
},
},
None,
));
NodeId::placeholder_from_expn_id(expn_id)
})
.collect::<Vec<_>>();
(item, derive_placeholders)
};
None,
));
NodeId::placeholder_from_expn_id(expn_id)
})
.collect::<Vec<_>>()
})
.unwrap_or_default();
let fragment = fragment_kind.expect_from_annotatables(::std::iter::once(item));
self.collect_invocations(fragment, &derive_placeholders)
}
ExpandResult::Retry(invoc) => {
if force {
self.cx.span_bug(
invoc.span(),
"expansion entered force mode but is still stuck",
);
} else {
// Cannot expand, will retry this invocation later.
undetermined_invocations.push((invoc, Some(ext)));
continue;
}
}
};
progress = true;
@ -596,29 +579,6 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
fragment_with_placeholders
}
fn error_derive_forbidden_on_non_adt(&self, derives: &[Path], item: &Annotatable) {
let attr = self.cx.sess.find_by_name(item.attrs(), sym::derive);
let span = attr.map_or(item.span(), |attr| attr.span);
let mut err = struct_span_err!(
self.cx.sess,
span,
E0774,
"`derive` may only be applied to structs, enums and unions",
);
if let Some(ast::Attribute { style: ast::AttrStyle::Inner, .. }) = attr {
let trait_list = derives.iter().map(|t| pprust::path_to_string(t)).collect::<Vec<_>>();
let suggestion = format!("#[derive({})]", trait_list.join(", "));
err.span_suggestion(
span,
"try an outer attribute",
suggestion,
// We don't 𝑘𝑛𝑜𝑤 that the following item is an ADT
Applicability::MaybeIncorrect,
);
}
err.emit();
}
fn resolve_imports(&mut self) {
if self.monotonic {
self.cx.resolver.resolve_imports();
@ -633,7 +593,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
&mut self,
mut fragment: AstFragment,
extra_placeholders: &[NodeId],
) -> (AstFragment, Vec<(Invocation, Option<InvocationRes>)>) {
) -> (AstFragment, Vec<(Invocation, Option<Lrc<SyntaxExtension>>)>) {
// Resolve `$crate`s in the fragment for pretty-printing.
self.cx.resolver.resolve_dollar_crates();
@ -733,7 +693,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
}
_ => unreachable!(),
},
InvocationKind::Attr { attr, mut item, derives, after_derive } => match ext {
InvocationKind::Attr { attr, mut item, derives } => match ext {
SyntaxExtensionKind::Attr(expander) => {
self.gate_proc_macro_input(&item);
self.gate_proc_macro_attr_item(span, &item);
@ -764,12 +724,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
ExpandResult::Retry(item) => {
// Reassemble the original invocation for retrying.
return ExpandResult::Retry(Invocation {
kind: InvocationKind::Attr {
attr,
item,
derives,
after_derive,
},
kind: InvocationKind::Attr { attr, item, derives },
..invoc
});
}
@ -813,7 +768,6 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
}
_ => unreachable!(),
},
InvocationKind::DeriveContainer { .. } => unreachable!(),
})
}
@ -1011,29 +965,13 @@ pub fn ensure_complete_parse<'a>(
struct InvocationCollector<'a, 'b> {
cx: &'a mut ExtCtxt<'b>,
cfg: StripUnconfigured<'a>,
invocations: Vec<(Invocation, Option<InvocationRes>)>,
invocations: Vec<(Invocation, Option<Lrc<SyntaxExtension>>)>,
monotonic: bool,
}
impl<'a, 'b> InvocationCollector<'a, 'b> {
fn collect(&mut self, fragment_kind: AstFragmentKind, kind: InvocationKind) -> AstFragment {
// Expansion data for all the collected invocations is set upon their resolution,
// with exception of the derive container case which is not resolved and can get
// its expansion data immediately.
let expn_data = match &kind {
InvocationKind::DeriveContainer { item, .. } => {
let mut expn_data = ExpnData::default(
ExpnKind::Macro(MacroKind::Attr, sym::derive),
item.span(),
self.cx.sess.parse_sess.edition,
None,
);
expn_data.parent = self.cx.current_expansion.id;
Some(expn_data)
}
_ => None,
};
let expn_id = ExpnId::fresh(expn_data);
let expn_id = ExpnId::fresh(None);
let vis = kind.placeholder_visibility();
self.invocations.push((
Invocation {
@ -1061,64 +999,44 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
fn collect_attr(
&mut self,
(attr, derives, after_derive): (Option<ast::Attribute>, Vec<Path>, bool),
(attr, derives): (ast::Attribute, Vec<Path>),
item: Annotatable,
kind: AstFragmentKind,
) -> AstFragment {
self.collect(
kind,
match attr {
Some(attr) => InvocationKind::Attr { attr, item, derives, after_derive },
None => InvocationKind::DeriveContainer { derives, item },
},
)
self.collect(kind, InvocationKind::Attr { attr, item, derives })
}
fn find_attr_invoc(
&self,
attrs: &mut Vec<ast::Attribute>,
after_derive: &mut bool,
) -> Option<ast::Attribute> {
attrs
.iter()
.position(|a| {
if a.has_name(sym::derive) {
*after_derive = true;
}
!self.cx.sess.is_attr_known(a) && !is_builtin_attr(a)
})
.map(|i| attrs.remove(i))
}
/// If `item` is an attribute invocation, remove the attribute and return it together with
/// derives following it. We have to collect the derives in order to resolve legacy derive
/// helpers (helpers written before derives that introduce them).
fn take_first_attr(&mut self, item: &mut impl HasAttrs) -> Option<(ast::Attribute, Vec<Path>)> {
let mut attr = None;
/// If `item` is an attr invocation, remove and return the macro attribute and derive traits.
fn take_first_attr(
&mut self,
item: &mut impl HasAttrs,
) -> Option<(Option<ast::Attribute>, Vec<Path>, /* after_derive */ bool)> {
let (mut attr, mut traits, mut after_derive) = (None, Vec::new(), false);
item.visit_attrs(|attrs| {
attr = attrs
.iter()
.position(|a| !self.cx.sess.is_attr_known(a) && !is_builtin_attr(a))
.map(|attr_pos| {
let attr = attrs.remove(attr_pos);
let following_derives = attrs[attr_pos..]
.iter()
.filter(|a| a.has_name(sym::derive))
.flat_map(|a| a.meta_item_list().unwrap_or_default())
.filter_map(|nested_meta| match nested_meta {
NestedMetaItem::MetaItem(ast::MetaItem {
kind: MetaItemKind::Word,
path,
..
}) => Some(path),
_ => None,
})
.collect();
item.visit_attrs(|mut attrs| {
attr = self.find_attr_invoc(&mut attrs, &mut after_derive);
traits = collect_derives(&mut self.cx, &mut attrs);
(attr, following_derives)
})
});
if attr.is_some() || !traits.is_empty() { Some((attr, traits, after_derive)) } else { None }
}
/// Alternative to `take_first_attr()` that ignores `#[derive]` so invocations fallthrough
/// to the unused-attributes lint (making it an error on statements and expressions
/// is a breaking change)
fn take_first_attr_no_derive(
&mut self,
nonitem: &mut impl HasAttrs,
) -> Option<(Option<ast::Attribute>, Vec<Path>, /* after_derive */ bool)> {
let (mut attr, mut after_derive) = (None, false);
nonitem.visit_attrs(|mut attrs| {
attr = self.find_attr_invoc(&mut attrs, &mut after_derive);
});
attr.map(|attr| (Some(attr), Vec::new(), after_derive))
attr
}
fn configure<T: HasAttrs>(&mut self, node: T) -> Option<T> {
@ -1132,17 +1050,6 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
for attr in attrs.iter() {
rustc_ast_passes::feature_gate::check_attribute(attr, self.cx.sess, features);
validate_attr::check_meta(&self.cx.sess.parse_sess, attr);
// macros are expanded before any lint passes so this warning has to be hardcoded
if attr.has_name(sym::derive) {
self.cx
.parse_sess()
.span_diagnostic
.struct_span_warn(attr.span, "`#[derive]` does nothing on macro invocations")
.note("this may become a hard error in a future release")
.emit();
}
if attr.doc_str().is_some() {
self.cx.sess.parse_sess.buffer_lint_with_diagnostic(
&UNUSED_DOC_COMMENTS,
@ -1162,12 +1069,10 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
visit_clobber(expr.deref_mut(), |mut expr| {
self.cfg.configure_expr_kind(&mut expr.kind);
if let Some(attr) = self.take_first_attr_no_derive(&mut expr) {
if let Some(attr) = self.take_first_attr(&mut expr) {
// Collect the invoc regardless of whether or not attributes are permitted here
// expansion will eat the attribute so it won't error later.
if let Some(attr) = attr.0.as_ref() {
self.cfg.maybe_emit_expr_attr_err(attr)
}
self.cfg.maybe_emit_expr_attr_err(&attr.0);
// AstFragmentKind::Expr requires the macro to emit an expression.
return self
@ -1263,10 +1168,8 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
expr.filter_map(|mut expr| {
self.cfg.configure_expr_kind(&mut expr.kind);
if let Some(attr) = self.take_first_attr_no_derive(&mut expr) {
if let Some(attr) = attr.0.as_ref() {
self.cfg.maybe_emit_expr_attr_err(attr)
}
if let Some(attr) = self.take_first_attr(&mut expr) {
self.cfg.maybe_emit_expr_attr_err(&attr.0);
return self
.collect_attr(attr, Annotatable::Expr(P(expr)), AstFragmentKind::OptExpr)
@ -1308,15 +1211,7 @@ impl<'a, 'b> MutVisitor for InvocationCollector<'a, 'b> {
// we'll expand attributes on expressions separately
if !stmt.is_expr() {
let attr = if stmt.is_item() {
self.take_first_attr(&mut stmt)
} else {
// Ignore derives on non-item statements for backwards compatibility.
// This will result in a unused attribute warning
self.take_first_attr_no_derive(&mut stmt)
};
if let Some(attr) = attr {
if let Some(attr) = self.take_first_attr(&mut stmt) {
return self
.collect_attr(attr, Annotatable::Stmt(P(stmt)), AstFragmentKind::Stmts)
.make_stmts();

View file

@ -1,16 +1,14 @@
use crate::base::{self, *};
use crate::proc_macro_server;
use rustc_ast as ast;
use rustc_ast::ptr::P;
use rustc_ast::token;
use rustc_ast::tokenstream::{CanSynthesizeMissingTokens, TokenStream, TokenTree};
use rustc_ast::{self as ast, *};
use rustc_data_structures::sync::Lrc;
use rustc_errors::{struct_span_err, Applicability, ErrorReported};
use rustc_lexer::is_ident;
use rustc_errors::ErrorReported;
use rustc_parse::nt_to_tokenstream;
use rustc_parse::parser::ForceCollect;
use rustc_span::symbol::sym;
use rustc_span::{Span, DUMMY_SP};
const EXEC_STRATEGY: pm::bridge::server::SameThread = pm::bridge::server::SameThread;
@ -142,91 +140,3 @@ impl MultiItemModifier for ProcMacroDerive {
ExpandResult::Ready(items)
}
}
crate fn collect_derives(cx: &mut ExtCtxt<'_>, attrs: &mut Vec<ast::Attribute>) -> Vec<ast::Path> {
let mut result = Vec::new();
attrs.retain(|attr| {
if !attr.has_name(sym::derive) {
return true;
}
// 1) First let's ensure that it's a meta item.
let nmis = match attr.meta_item_list() {
None => {
cx.struct_span_err(attr.span, "malformed `derive` attribute input")
.span_suggestion(
attr.span,
"missing traits to be derived",
"#[derive(Trait1, Trait2, ...)]".to_owned(),
Applicability::HasPlaceholders,
)
.emit();
return false;
}
Some(x) => x,
};
let mut error_reported_filter_map = false;
let mut error_reported_map = false;
let traits = nmis
.into_iter()
// 2) Moreover, let's ensure we have a path and not `#[derive("foo")]`.
.filter_map(|nmi| match nmi {
NestedMetaItem::Literal(lit) => {
error_reported_filter_map = true;
let mut err = struct_span_err!(
cx.sess,
lit.span,
E0777,
"expected path to a trait, found literal",
);
let token = lit.token.to_string();
if token.starts_with('"')
&& token.len() > 2
&& is_ident(&token[1..token.len() - 1])
{
err.help(&format!("try using `#[derive({})]`", &token[1..token.len() - 1]));
} else {
err.help("for example, write `#[derive(Debug)]` for `Debug`");
}
err.emit();
None
}
NestedMetaItem::MetaItem(mi) => Some(mi),
})
// 3) Finally, we only accept `#[derive($path_0, $path_1, ..)]`
// but not e.g. `#[derive($path_0 = "value", $path_1(abc))]`.
// In this case we can still at least determine that the user
// wanted this trait to be derived, so let's keep it.
.map(|mi| {
let mut traits_dont_accept = |title, action| {
error_reported_map = true;
let sp = mi.span.with_lo(mi.path.span.hi());
cx.struct_span_err(sp, title)
.span_suggestion(
sp,
action,
String::new(),
Applicability::MachineApplicable,
)
.emit();
};
match &mi.kind {
MetaItemKind::List(..) => traits_dont_accept(
"traits in `#[derive(...)]` don't accept arguments",
"remove the arguments",
),
MetaItemKind::NameValue(..) => traits_dont_accept(
"traits in `#[derive(...)]` don't accept values",
"remove the value",
),
MetaItemKind::Word => {}
}
mi.path
});
result.extend(traits);
!error_reported_filter_map && !error_reported_map
});
result
}

View file

@ -188,7 +188,6 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
ungated!(reexport_test_harness_main, Normal, template!(NameValueStr: "name")),
// Macros:
ungated!(derive, Normal, template!(List: "Trait1, Trait2, ...")),
ungated!(automatically_derived, Normal, template!(Word)),
// FIXME(#14407)
ungated!(macro_use, Normal, template!(Word, List: "name1, name2, ...")),

View file

@ -636,6 +636,9 @@ pub trait LintContext: Sized {
db.span_label(span, "ABI should be specified here");
db.help(&format!("the default ABI is {}", default_abi.name()));
}
BuiltinLintDiagnostics::LegacyDeriveHelpers(span) => {
db.span_label(span, "the attribute is introduced here");
}
}
// Rewrap `db`, and pass control to the user.
decorate(LintDiagnosticBuilder::new(db));

View file

@ -1,12 +1,11 @@
// ignore-tidy-filelength
//! Some lints that are built in to the compiler.
//!
//! These are the built-in lints that are emitted direct in the main
//! compiler code, rather than using their own custom pass. Those
//! lints are all available in `rustc_lint::builtin`.
// ignore-tidy-filelength
use crate::{declare_lint, declare_lint_pass};
use rustc_span::edition::Edition;
use rustc_span::symbol::sym;
@ -2922,6 +2921,52 @@ declare_lint! {
};
}
declare_lint! {
/// The `legacy_derive_helpers` lint detects derive helper attributes
/// that are used before they are introduced.
///
/// ### Example
///
/// ```rust,ignore (needs extern crate)
/// #[serde(rename_all = "camelCase")]
/// #[derive(Deserialize)]
/// struct S { /* fields */ }
/// ```
///
/// produces:
///
/// ```text
/// warning: derive helper attribute is used before it is introduced
/// --> $DIR/legacy-derive-helpers.rs:1:3
/// |
/// 1 | #[serde(rename_all = "camelCase")]
/// | ^^^^^
/// ...
/// 2 | #[derive(Deserialize)]
/// | ----------- the attribute is introduced here
/// ```
///
/// ### Explanation
///
/// Attributes like this work for historical reasons, but attribute expansion works in
/// left-to-right order in general, so, to resolve `#[serde]`, compiler has to try to "look
/// into the future" at not yet expanded part of the item , but such attempts are not always
/// reliable.
///
/// To fix the warning place the helper attribute after its corresponding derive.
/// ```rust,ignore (needs extern crate)
/// #[derive(Deserialize)]
/// #[serde(rename_all = "camelCase")]
/// struct S { /* fields */ }
/// ```
pub LEGACY_DERIVE_HELPERS,
Warn,
"detects derive helper attributes that are used before they are introduced",
@future_incompatible = FutureIncompatibleInfo {
reference: "issue #79202 <https://github.com/rust-lang/rust/issues/79202>",
};
}
declare_lint_pass! {
/// Does nothing as a lint pass, but registers some `Lint`s
/// that are used by other parts of the compiler.
@ -3012,6 +3057,7 @@ declare_lint_pass! {
MISSING_ABI,
SEMICOLON_IN_EXPRESSIONS_FROM_MACROS,
DISJOINT_CAPTURE_DROP_REORDER,
LEGACY_DERIVE_HELPERS,
]
}

View file

@ -256,6 +256,7 @@ pub enum BuiltinLintDiagnostics {
MissingAbi(Span, Abi),
UnusedDocComment(Span),
PatternsInFnsWithoutBody(Span, Ident),
LegacyDeriveHelpers(Span),
}
/// Lints that are buffered up early on in the `Session` before the

View file

@ -306,13 +306,11 @@ impl<'a> Parser<'a> {
}
pub fn maybe_needs_tokens(attrs: &[ast::Attribute]) -> bool {
// One of the attributes may either itself be a macro, or apply derive macros (`derive`),
// One of the attributes may either itself be a macro,
// or expand to macro attributes (`cfg_attr`).
attrs.iter().any(|attr| {
attr.ident().map_or(true, |ident| {
ident.name == sym::derive
|| ident.name == sym::cfg_attr
|| !rustc_feature::is_builtin_attr_name(ident.name)
ident.name == sym::cfg_attr || !rustc_feature::is_builtin_attr_name(ident.name)
})
})
}

View file

@ -967,6 +967,8 @@ pub struct Resolver<'a> {
output_macro_rules_scopes: FxHashMap<ExpnId, MacroRulesScopeRef<'a>>,
/// Helper attributes that are in scope for the given expansion.
helper_attrs: FxHashMap<ExpnId, Vec<Ident>>,
/// Resolutions for paths inside the `#[derive(...)]` attribute with the given `ExpnId`.
derive_resolutions: FxHashMap<ExpnId, Vec<(Lrc<SyntaxExtension>, ast::Path)>>,
/// Avoid duplicated errors for "name already defined".
name_already_seen: FxHashMap<Symbol, Span>,
@ -1295,6 +1297,7 @@ impl<'a> Resolver<'a> {
invocation_parent_scopes: Default::default(),
output_macro_rules_scopes: Default::default(),
helper_attrs: Default::default(),
derive_resolutions: Default::default(),
local_macro_def_scopes: FxHashMap::default(),
name_already_seen: FxHashMap::default(),
potentially_unused_imports: Vec::new(),

View file

@ -14,8 +14,7 @@ use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::ptr_key::PtrKey;
use rustc_data_structures::sync::Lrc;
use rustc_errors::struct_span_err;
use rustc_expand::base::{Indeterminate, InvocationRes, ResolverExpand};
use rustc_expand::base::{SyntaxExtension, SyntaxExtensionKind};
use rustc_expand::base::{Indeterminate, ResolverExpand, SyntaxExtension, SyntaxExtensionKind};
use rustc_expand::compile_declarative_macro;
use rustc_expand::expand::{AstFragment, Invocation, InvocationKind};
use rustc_feature::is_builtin_attr_name;
@ -24,7 +23,8 @@ use rustc_hir::def_id;
use rustc_hir::PrimTy;
use rustc_middle::middle::stability;
use rustc_middle::ty;
use rustc_session::lint::builtin::{SOFT_UNSTABLE, UNUSED_MACROS};
use rustc_session::lint::builtin::{LEGACY_DERIVE_HELPERS, SOFT_UNSTABLE, UNUSED_MACROS};
use rustc_session::lint::BuiltinLintDiagnostics;
use rustc_session::parse::feature_err;
use rustc_session::Session;
use rustc_span::edition::Edition;
@ -227,7 +227,7 @@ impl<'a> ResolverExpand for Resolver<'a> {
invoc: &Invocation,
eager_expansion_root: ExpnId,
force: bool,
) -> Result<InvocationRes, Indeterminate> {
) -> Result<Lrc<SyntaxExtension>, Indeterminate> {
let invoc_id = invoc.expansion_data.id;
let parent_scope = match self.invocation_parent_scopes.get(&invoc_id) {
Some(parent_scope) => *parent_scope,
@ -244,65 +244,15 @@ impl<'a> ResolverExpand for Resolver<'a> {
}
};
let (path, kind, inner_attr, derives, after_derive) = match invoc.kind {
InvocationKind::Attr { ref attr, ref derives, after_derive, .. } => (
let (path, kind, inner_attr, derives) = match invoc.kind {
InvocationKind::Attr { ref attr, ref derives, .. } => (
&attr.get_normal_item().path,
MacroKind::Attr,
attr.style == ast::AttrStyle::Inner,
self.arenas.alloc_ast_paths(derives),
after_derive,
),
InvocationKind::Bang { ref mac, .. } => {
(&mac.path, MacroKind::Bang, false, &[][..], false)
}
InvocationKind::Derive { ref path, .. } => {
(path, MacroKind::Derive, false, &[][..], false)
}
InvocationKind::DeriveContainer { ref derives, .. } => {
// Block expansion of the container until we resolve all derives in it.
// This is required for two reasons:
// - Derive helper attributes are in scope for the item to which the `#[derive]`
// is applied, so they have to be produced by the container's expansion rather
// than by individual derives.
// - Derives in the container need to know whether one of them is a built-in `Copy`.
// FIXME: Try to avoid repeated resolutions for derives here and in expansion.
let mut exts = Vec::new();
let mut helper_attrs = Vec::new();
for path in derives {
exts.push(
match self.resolve_macro_path(
path,
Some(MacroKind::Derive),
&parent_scope,
true,
force,
) {
Ok((Some(ext), _)) => {
let span = path
.segments
.last()
.unwrap()
.ident
.span
.normalize_to_macros_2_0();
helper_attrs.extend(
ext.helper_attrs.iter().map(|name| Ident::new(*name, span)),
);
if ext.builtin_name == Some(sym::Copy) {
self.containers_deriving_copy.insert(invoc_id);
}
ext
}
Ok(_) | Err(Determinacy::Determined) => {
self.dummy_ext(MacroKind::Derive)
}
Err(Determinacy::Undetermined) => return Err(Indeterminate),
},
)
}
self.helper_attrs.insert(invoc_id, helper_attrs);
return Ok(InvocationRes::DeriveContainer(exts));
}
InvocationKind::Bang { ref mac, .. } => (&mac.path, MacroKind::Bang, false, &[][..]),
InvocationKind::Derive { ref path, .. } => (path, MacroKind::Derive, false, &[][..]),
};
// Derives are not included when `invocations` are collected, so we have to add them here.
@ -328,14 +278,11 @@ impl<'a> ResolverExpand for Resolver<'a> {
));
if let Res::Def(_, _) = res {
if after_derive {
self.session.span_err(span, "macro attributes must be placed before `#[derive]`");
}
let normal_module_def_id = self.macro_def_scope(invoc_id).nearest_parent_mod;
self.definitions.add_parent_module_of_macro_def(invoc_id, normal_module_def_id);
}
Ok(InvocationRes::Single(ext))
Ok(ext)
}
fn check_unused_macros(&mut self) {
@ -344,7 +291,7 @@ impl<'a> ResolverExpand for Resolver<'a> {
}
}
fn lint_node_id(&mut self, expn_id: ExpnId) -> NodeId {
fn lint_node_id(&self, expn_id: ExpnId) -> NodeId {
// FIXME - make this more precise. This currently returns the NodeId of the
// nearest closing item - we should try to return the closest parent of the ExpnId
self.invocation_parents
@ -356,6 +303,63 @@ impl<'a> ResolverExpand for Resolver<'a> {
self.containers_deriving_copy.contains(&expn_id)
}
fn resolve_derives(
&mut self,
expn_id: ExpnId,
derives: Vec<ast::Path>,
force: bool,
) -> Result<(), Indeterminate> {
// Block expansion of the container until we resolve all derives in it.
// This is required for two reasons:
// - Derive helper attributes are in scope for the item to which the `#[derive]`
// is applied, so they have to be produced by the container's expansion rather
// than by individual derives.
// - Derives in the container need to know whether one of them is a built-in `Copy`.
// FIXME: Try to cache intermediate results to avoid resolving same derives multiple times.
let parent_scope = self.invocation_parent_scopes[&expn_id];
let mut exts = Vec::new();
let mut helper_attrs = Vec::new();
let mut has_derive_copy = false;
for path in derives {
exts.push((
match self.resolve_macro_path(
&path,
Some(MacroKind::Derive),
&parent_scope,
true,
force,
) {
Ok((Some(ext), _)) => {
let span =
path.segments.last().unwrap().ident.span.normalize_to_macros_2_0();
helper_attrs
.extend(ext.helper_attrs.iter().map(|name| Ident::new(*name, span)));
has_derive_copy |= ext.builtin_name == Some(sym::Copy);
ext
}
Ok(_) | Err(Determinacy::Determined) => self.dummy_ext(MacroKind::Derive),
Err(Determinacy::Undetermined) => return Err(Indeterminate),
},
path,
))
}
self.derive_resolutions.insert(expn_id, exts);
self.helper_attrs.insert(expn_id, helper_attrs);
// Mark this derive as having `Copy` either if it has `Copy` itself or if its parent derive
// has `Copy`, to support cases like `#[derive(Clone, Copy)] #[derive(Debug)]`.
if has_derive_copy || self.has_derive_copy(parent_scope.expansion) {
self.containers_deriving_copy.insert(expn_id);
}
Ok(())
}
fn take_derive_resolutions(
&mut self,
expn_id: ExpnId,
) -> Option<Vec<(Lrc<SyntaxExtension>, ast::Path)>> {
self.derive_resolutions.remove(&expn_id)
}
// The function that implements the resolution logic of `#[cfg_accessible(path)]`.
// Returns true if the path can certainly be resolved in one of three namespaces,
// returns false if the path certainly cannot be resolved in any of the three namespaces.
@ -818,6 +822,8 @@ impl<'a> Resolver<'a> {
let is_builtin = |res| {
matches!(res, Res::NonMacroAttr(NonMacroAttrKind::Builtin(..)))
};
let derive_helper =
Res::NonMacroAttr(NonMacroAttrKind::DeriveHelper);
let derive_helper_compat =
Res::NonMacroAttr(NonMacroAttrKind::DeriveHelperCompat);
@ -826,7 +832,7 @@ impl<'a> Resolver<'a> {
} else if is_builtin(innermost_res) || is_builtin(res) {
Some(AmbiguityKind::BuiltinAttr)
} else if innermost_res == derive_helper_compat
|| res == derive_helper_compat
|| res == derive_helper_compat && innermost_res != derive_helper
{
Some(AmbiguityKind::DeriveHelper)
} else if innermost_flags.contains(Flags::MACRO_RULES)
@ -992,6 +998,15 @@ impl<'a> Resolver<'a> {
let res = binding.res();
let seg = Segment::from_ident(ident);
check_consistency(self, &[seg], ident.span, kind, initial_res, res);
if res == Res::NonMacroAttr(NonMacroAttrKind::DeriveHelperCompat) {
self.lint_buffer.buffer_lint_with_diagnostic(
LEGACY_DERIVE_HELPERS,
self.lint_node_id(parent_scope.expansion),
ident.span,
"derive helper attribute is used before it is introduced",
BuiltinLintDiagnostics::LegacyDeriveHelpers(binding.span),
);
}
}
Err(..) => {
let expected = kind.descr_expected();
@ -1078,7 +1093,7 @@ impl<'a> Resolver<'a> {
crate fn check_reserved_macro_name(&mut self, ident: Ident, res: Res) {
// Reserve some names that are not quite covered by the general check
// performed on `Resolver::builtin_attrs`.
if ident.name == sym::cfg || ident.name == sym::cfg_attr || ident.name == sym::derive {
if ident.name == sym::cfg || ident.name == sym::cfg_attr {
let macro_kind = self.get_macro(res).map(|ext| ext.macro_kind());
if macro_kind.is_some() && sub_namespace_match(macro_kind, Some(MacroKind::Attr)) {
self.session.span_err(