| use rustc_ast as ast; |
| use rustc_ast::{GenericParamKind, ItemKind, MetaItemInner, MetaItemKind, StmtKind}; |
| use rustc_expand::base::{ |
| Annotatable, DeriveResolution, ExpandResult, ExtCtxt, Indeterminate, MultiItemModifier, |
| }; |
| use rustc_feature::AttributeTemplate; |
| use rustc_parse::validate_attr; |
| use rustc_session::Session; |
| use rustc_span::{ErrorGuaranteed, Ident, Span, sym}; |
| |
| use crate::cfg_eval::cfg_eval; |
| use crate::errors; |
| |
| pub(crate) struct Expander { |
| pub is_const: bool, |
| } |
| |
| impl MultiItemModifier for Expander { |
| fn expand( |
| &self, |
| ecx: &mut ExtCtxt<'_>, |
| span: Span, |
| meta_item: &ast::MetaItem, |
| item: Annotatable, |
| _: bool, |
| ) -> ExpandResult<Vec<Annotatable>, Annotatable> { |
| let sess = ecx.sess; |
| if report_bad_target(sess, &item, span).is_err() { |
| // 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 (sess, features) = (ecx.sess, ecx.ecfg.features); |
| let result = |
| ecx.resolver.resolve_derives(ecx.current_expansion.id, ecx.force_mode, &|| { |
| let template = |
| AttributeTemplate { list: Some("Trait1, Trait2, ..."), ..Default::default() }; |
| validate_attr::check_builtin_meta_item( |
| &sess.psess, |
| meta_item, |
| ast::AttrStyle::Outer, |
| sym::derive, |
| template, |
| true, |
| ); |
| |
| let mut resolutions = match &meta_item.kind { |
| MetaItemKind::List(list) => { |
| list.iter() |
| .filter_map(|meta_item_inner| match meta_item_inner { |
| MetaItemInner::MetaItem(meta) => Some(meta), |
| MetaItemInner::Lit(lit) => { |
| // Reject `#[derive("Debug")]`. |
| report_unexpected_meta_item_lit(sess, lit); |
| None |
| } |
| }) |
| .map(|meta| { |
| // Reject `#[derive(Debug = "value", Debug(abc))]`, but recover the |
| // paths. |
| report_path_args(sess, meta); |
| meta.path.clone() |
| }) |
| .map(|path| DeriveResolution { |
| path, |
| item: dummy_annotatable(), |
| exts: None, |
| is_const: self.is_const, |
| }) |
| .collect() |
| } |
| _ => vec![], |
| }; |
| |
| // Do not configure or clone items unless necessary. |
| match &mut resolutions[..] { |
| [] => {} |
| [first, others @ ..] => { |
| first.item = cfg_eval( |
| sess, |
| features, |
| item.clone(), |
| ecx.current_expansion.lint_node_id, |
| ); |
| for other in others { |
| other.item = first.item.clone(); |
| } |
| } |
| } |
| |
| resolutions |
| }); |
| |
| match result { |
| Ok(()) => ExpandResult::Ready(vec![item]), |
| Err(Indeterminate) => ExpandResult::Retry(item), |
| } |
| } |
| } |
| |
| // The cheapest `Annotatable` to construct. |
| fn dummy_annotatable() -> Annotatable { |
| Annotatable::GenericParam(ast::GenericParam { |
| id: ast::DUMMY_NODE_ID, |
| ident: Ident::dummy(), |
| attrs: Default::default(), |
| bounds: Default::default(), |
| is_placeholder: false, |
| kind: GenericParamKind::Lifetime, |
| colon_span: None, |
| }) |
| } |
| |
| fn report_bad_target( |
| sess: &Session, |
| item: &Annotatable, |
| span: Span, |
| ) -> Result<(), ErrorGuaranteed> { |
| 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 { |
| return Err(sess.dcx().emit_err(errors::BadDeriveTarget { span, item: item.span() })); |
| } |
| Ok(()) |
| } |
| |
| fn report_unexpected_meta_item_lit(sess: &Session, lit: &ast::MetaItemLit) { |
| let help = match lit.kind { |
| ast::LitKind::Str(_, ast::StrStyle::Cooked) |
| if rustc_lexer::is_ident(lit.symbol.as_str()) => |
| { |
| errors::BadDeriveLitHelp::StrLit { sym: lit.symbol } |
| } |
| _ => errors::BadDeriveLitHelp::Other, |
| }; |
| sess.dcx().emit_err(errors::BadDeriveLit { span: lit.span, help }); |
| } |
| |
| fn report_path_args(sess: &Session, meta: &ast::MetaItem) { |
| let span = meta.span.with_lo(meta.path.span.hi()); |
| |
| match meta.kind { |
| MetaItemKind::Word => {} |
| MetaItemKind::List(..) => { |
| sess.dcx().emit_err(errors::DerivePathArgsList { span }); |
| } |
| MetaItemKind::NameValue(..) => { |
| sess.dcx().emit_err(errors::DerivePathArgsValue { span }); |
| } |
| } |
| } |