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, 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::empty(), 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 }); } } }