diff options
| author | Vadim Petrochenkov <vadim.petrochenkov@gmail.com> | 2020-11-14 14:47:14 +0300 |
|---|---|---|
| committer | Vadim Petrochenkov <vadim.petrochenkov@gmail.com> | 2021-02-07 20:08:45 +0300 |
| commit | dbdbd30bf2cb0d48c8bbce83c2458592664dbb18 (patch) | |
| tree | 92877e16f45e3cf927ffc4296e0f90c48ec036f5 /compiler | |
| parent | ae00b62ceb7eaf1f02f5289ab233bf7e0e8060d5 (diff) | |
| download | rust-dbdbd30bf2cb0d48c8bbce83c2458592664dbb18.tar.gz rust-dbdbd30bf2cb0d48c8bbce83c2458592664dbb18.zip | |
expand/resolve: Turn `#[derive]` into a regular macro attribute
Diffstat (limited to 'compiler')
| -rw-r--r-- | compiler/rustc_builtin_macros/Cargo.toml | 1 | ||||
| -rw-r--r-- | compiler/rustc_builtin_macros/src/derive.rs | 132 | ||||
| -rw-r--r-- | compiler/rustc_builtin_macros/src/lib.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_expand/src/base.rs | 44 | ||||
| -rw-r--r-- | compiler/rustc_expand/src/expand.rs | 339 | ||||
| -rw-r--r-- | compiler/rustc_expand/src/proc_macro.rs | 94 | ||||
| -rw-r--r-- | compiler/rustc_feature/src/builtin_attrs.rs | 1 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/context.rs | 3 | ||||
| -rw-r--r-- | compiler/rustc_lint_defs/src/builtin.rs | 50 | ||||
| -rw-r--r-- | compiler/rustc_lint_defs/src/lib.rs | 1 | ||||
| -rw-r--r-- | compiler/rustc_parse/src/parser/attr.rs | 6 | ||||
| -rw-r--r-- | compiler/rustc_resolve/src/lib.rs | 3 | ||||
| -rw-r--r-- | compiler/rustc_resolve/src/macros.rs | 145 |
13 files changed, 407 insertions, 414 deletions
diff --git a/compiler/rustc_builtin_macros/Cargo.toml b/compiler/rustc_builtin_macros/Cargo.toml index c397a854126..eb022b5b2b1 100644 --- a/compiler/rustc_builtin_macros/Cargo.toml +++ b/compiler/rustc_builtin_macros/Cargo.toml @@ -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" } diff --git a/compiler/rustc_builtin_macros/src/derive.rs b/compiler/rustc_builtin_macros/src/derive.rs new file mode 100644 index 00000000000..fad64858ce3 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/derive.rs @@ -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") + } + } +} diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs index 59844b6c692..9a3c914337c 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -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, diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index 08543d1622a..35ddb1fb9bc 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -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>; } diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 5fdb7fc5915..870b5c92d89 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -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 }, - }, - ) - } - - 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 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(|mut attrs| { - attr = self.find_attr_invoc(&mut attrs, &mut after_derive); - traits = collect_derives(&mut self.cx, &mut attrs); - }); - - if attr.is_some() || !traits.is_empty() { Some((attr, traits, after_derive)) } else { None } + self.collect(kind, InvocationKind::Attr { attr, item, derives }) } - /// 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); + /// 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; + + 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(); + + (attr, following_derives) + }) }); - 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(); diff --git a/compiler/rustc_expand/src/proc_macro.rs b/compiler/rustc_expand/src/proc_macro.rs index 6779734cfc1..8cbaa7c945a 100644 --- a/compiler/rustc_expand/src/proc_macro.rs +++ b/compiler/rustc_expand/src/proc_macro.rs @@ -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 -} diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 3ed5320da73..ac50703b544 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -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, ...")), diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index 58a9064b919..254220839aa 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -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)); diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index da62ad3a6b1..f0a5ea150b7 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -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, ] } diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 9d60a51a0af..594e2cbd3ae 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -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 diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs index 1b26fb33370..523eb9dba35 100644 --- a/compiler/rustc_parse/src/parser/attr.rs +++ b/compiler/rustc_parse/src/parser/attr.rs @@ -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) }) }) } diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 2be669a1726..819fabdd1f1 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -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(), diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index d76db7c645a..6d3fde33f4d 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -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( |
