| use core::ops::ControlFlow; | |
| use rustc_ast::visit::visit_opt; | |
| use rustc_ast::{self as ast, EnumDef, Safety, VariantData, attr}; | |
| use rustc_expand::base::{Annotatable, DummyResult, ExtCtxt}; | |
| use rustc_span::{ErrorGuaranteed, Ident, Span, kw, sym}; | |
| use smallvec::SmallVec; | |
| use thin_vec::{ThinVec, thin_vec}; | |
| use crate::deriving::generic::ty::*; | |
| use crate::deriving::generic::*; | |
| use crate::diagnostics; | |
| pub(crate) fn expand_deriving_default( | |
| cx: &ExtCtxt<'_>, | |
| span: Span, | |
| mitem: &ast::MetaItem, | |
| item: &Annotatable, | |
| push: &mut dyn FnMut(Annotatable), | |
| is_const: bool, | |
| ) { | |
| item.visit_with(&mut DetectNonVariantDefaultAttr { cx }); | |
| let trait_def = TraitDef { | |
| span, | |
| path: Path::new(vec![kw::Default, sym::Default]), | |
| skip_path_as_bound: has_a_default_variant(item), | |
| needs_copy_as_bound_if_packed: false, | |
| additional_bounds: Vec::new(), | |
| supports_unions: false, | |
| methods: vec![MethodDef { | |
| name: kw::Default, | |
| generics: Bounds::empty(), | |
| explicit_self: false, | |
| nonself_args: Vec::new(), | |
| ret_ty: Self_, | |
| attributes: thin_vec![cx.attr_word(sym::inline, span)], | |
| fieldless_variants_strategy: FieldlessVariantsStrategy::Default, | |
| combine_substructure: combine_substructure(Box::new(|cx, trait_span, substr| { | |
| match substr.fields { | |
| StaticStruct(_, fields) => { | |
| default_struct_substructure(cx, trait_span, substr, fields) | |
| } | |
| StaticEnum(enum_def) => { | |
| default_enum_substructure(cx, trait_span, enum_def, item.span()) | |
| } | |
| _ => cx.dcx().span_bug(trait_span, "method in `derive(Default)`"), | |
| } | |
| })), | |
| }], | |
| associated_types: Vec::new(), | |
| is_const, | |
| is_staged_api_crate: cx.ecfg.features.staged_api(), | |
| safety: Safety::Default, | |
| document: true, | |
| }; | |
| trait_def.expand(cx, mitem, item, push) | |
| } | |
| fn default_call(cx: &ExtCtxt<'_>, span: Span) -> Box<ast::Expr> { | |
| // Note that `kw::Default` is "default" and `sym::Default` is "Default"! | |
| let default_ident = cx.std_path(&[kw::Default, sym::Default, kw::Default]); | |
| cx.expr_call_global(span, default_ident, ThinVec::new()) | |
| } | |
| fn default_struct_substructure( | |
| cx: &ExtCtxt<'_>, | |
| trait_span: Span, | |
| substr: &Substructure<'_>, | |
| summary: &StaticFields<'_>, | |
| ) -> BlockOrExpr { | |
| let expr = match summary { | |
| Unnamed(_, IsTuple::No) => cx.expr_ident(trait_span, substr.type_ident), | |
| Unnamed(fields, IsTuple::Yes) => { | |
| let exprs = fields.iter().map(|sp| default_call(cx, *sp)).collect(); | |
| cx.expr_call_ident(trait_span, substr.type_ident, exprs) | |
| } | |
| Named(fields) => { | |
| let default_fields = fields | |
| .iter() | |
| .map(|&(ident, span, default_val)| { | |
| let value = match default_val { | |
| // We use `Default::default()`. | |
| None => default_call(cx, span), | |
| // We use the field default const expression. | |
| Some(val) => { | |
| cx.expr(val.value.span, ast::ExprKind::ConstBlock(val.clone())) | |
| } | |
| }; | |
| cx.field_imm(span, ident, value) | |
| }) | |
| .collect(); | |
| cx.expr_struct_ident(trait_span, substr.type_ident, default_fields) | |
| } | |
| }; | |
| BlockOrExpr::new_expr(expr) | |
| } | |
| fn default_enum_substructure( | |
| cx: &ExtCtxt<'_>, | |
| trait_span: Span, | |
| enum_def: &EnumDef, | |
| item_span: Span, | |
| ) -> BlockOrExpr { | |
| let expr = match try { | |
| let default_variant = extract_default_variant(cx, enum_def, trait_span, item_span)?; | |
| validate_default_attribute(cx, default_variant)?; | |
| default_variant | |
| } { | |
| Ok(default_variant) => { | |
| // We now know there is exactly one unit variant with exactly one `#[default]` attribute. | |
| match &default_variant.data { | |
| VariantData::Unit(_) => cx.expr_path(cx.path( | |
| default_variant.span, | |
| vec![Ident::new(kw::SelfUpper, default_variant.span), default_variant.ident], | |
| )), | |
| VariantData::Struct { fields, .. } => { | |
| // This only happens if `#![feature(default_field_values)]`. We have validated | |
| // all fields have default values in the definition. | |
| let default_fields = fields | |
| .iter() | |
| .map(|field| { | |
| cx.field_imm( | |
| field.span, | |
| field.ident.unwrap(), | |
| match &field.default { | |
| // We use `Default::default()`. | |
| None => default_call(cx, field.span), | |
| // We use the field default const expression. | |
| Some(val) => cx.expr( | |
| val.value.span, | |
| ast::ExprKind::ConstBlock(val.clone()), | |
| ), | |
| }, | |
| ) | |
| }) | |
| .collect(); | |
| let path = cx.path( | |
| default_variant.span, | |
| vec![ | |
| Ident::new(kw::SelfUpper, default_variant.span), | |
| default_variant.ident, | |
| ], | |
| ); | |
| cx.expr_struct(default_variant.span, path, default_fields) | |
| } | |
| // Logic error in `extract_default_variant`. | |
| VariantData::Tuple(..) => { | |
| cx.dcx().bug("encountered tuple variant annotated with `#[default]`") | |
| } | |
| } | |
| } | |
| Err(guar) => DummyResult::raw_expr(trait_span, Some(guar)), | |
| }; | |
| BlockOrExpr::new_expr(expr) | |
| } | |
| fn extract_default_variant<'a>( | |
| cx: &ExtCtxt<'_>, | |
| enum_def: &'a EnumDef, | |
| trait_span: Span, | |
| item_span: Span, | |
| ) -> Result<&'a rustc_ast::Variant, ErrorGuaranteed> { | |
| let default_variants: SmallVec<[_; 1]> = enum_def | |
| .variants | |
| .iter() | |
| .filter(|variant| attr::contains_name(&variant.attrs, kw::Default)) | |
| .collect(); | |
| let variant = match default_variants.as_slice() { | |
| [variant] => variant, | |
| [] => { | |
| let possible_defaults = enum_def | |
| .variants | |
| .iter() | |
| .filter(|variant| matches!(variant.data, VariantData::Unit(..))) | |
| .filter(|variant| !attr::contains_name(&variant.attrs, sym::non_exhaustive)); | |
| let suggs = possible_defaults | |
| .map(|v| diagnostics::NoDefaultVariantSugg { span: v.span.shrink_to_lo() }) | |
| .collect(); | |
| let guar = cx.dcx().emit_err(diagnostics::NoDefaultVariant { | |
| span: trait_span, | |
| item_span, | |
| suggs, | |
| }); | |
| return Err(guar); | |
| } | |
| [first, rest @ ..] => { | |
| let suggs = default_variants | |
| .iter() | |
| .filter_map(|variant| { | |
| let keep = attr::find_by_name(&variant.attrs, kw::Default)?.span; | |
| let spans: Vec<Span> = default_variants | |
| .iter() | |
| .flat_map(|v| { | |
| attr::filter_by_name(&v.attrs, kw::Default) | |
| .filter_map(|attr| (attr.span != keep).then_some(attr.span)) | |
| }) | |
| .collect(); | |
| (!spans.is_empty()).then_some(diagnostics::MultipleDefaultsSugg { | |
| spans, | |
| ident: variant.ident, | |
| }) | |
| }) | |
| .collect(); | |
| let guar = cx.dcx().emit_err(diagnostics::MultipleDefaults { | |
| span: trait_span, | |
| first: first.span, | |
| additional: rest.iter().map(|v| v.span).collect(), | |
| suggs, | |
| }); | |
| return Err(guar); | |
| } | |
| }; | |
| if cx.ecfg.features.default_field_values() | |
| && let VariantData::Struct { fields, .. } = &variant.data | |
| && fields.iter().all(|f| f.default.is_some()) | |
| // Disallow `#[default] Variant {}` | |
| && !fields.is_empty() | |
| { | |
| // Allowed | |
| } else if !matches!(variant.data, VariantData::Unit(..)) { | |
| let post = if cx.ecfg.features.default_field_values() { | |
| " or variants where every field has a default value" | |
| } else { | |
| "" | |
| }; | |
| let guar = | |
| cx.dcx().emit_err(diagnostics::NonUnitDefault { span: variant.ident.span, post }); | |
| return Err(guar); | |
| } | |
| if let Some(non_exhaustive_attr) = attr::find_by_name(&variant.attrs, sym::non_exhaustive) { | |
| let guar = cx.dcx().emit_err(diagnostics::NonExhaustiveDefault { | |
| span: variant.ident.span, | |
| non_exhaustive: non_exhaustive_attr.span, | |
| }); | |
| return Err(guar); | |
| } | |
| Ok(variant) | |
| } | |
| fn validate_default_attribute( | |
| cx: &ExtCtxt<'_>, | |
| default_variant: &rustc_ast::Variant, | |
| ) -> Result<(), ErrorGuaranteed> { | |
| let attrs: SmallVec<[_; 1]> = | |
| attr::filter_by_name(&default_variant.attrs, kw::Default).collect(); | |
| let attr = match attrs.as_slice() { | |
| [attr] => attr, | |
| [] => cx.dcx().bug( | |
| "this method must only be called with a variant that has a `#[default]` attribute", | |
| ), | |
| [first, rest @ ..] => { | |
| let sugg = diagnostics::MultipleDefaultAttrsSugg { | |
| spans: rest.iter().map(|attr| attr.span).collect(), | |
| }; | |
| let guar = cx.dcx().emit_err(diagnostics::MultipleDefaultAttrs { | |
| span: default_variant.ident.span, | |
| first: first.span, | |
| first_rest: rest[0].span, | |
| rest: rest.iter().map(|attr| attr.span).collect::<Vec<_>>().into(), | |
| only_one: rest.len() == 1, | |
| sugg, | |
| }); | |
| return Err(guar); | |
| } | |
| }; | |
| if !attr.is_word() { | |
| let guar = cx.dcx().emit_err(diagnostics::DefaultHasArg { span: attr.span }); | |
| return Err(guar); | |
| } | |
| Ok(()) | |
| } | |
| struct DetectNonVariantDefaultAttr<'a, 'b> { | |
| cx: &'a ExtCtxt<'b>, | |
| } | |
| impl<'a, 'b> rustc_ast::visit::Visitor<'a> for DetectNonVariantDefaultAttr<'a, 'b> { | |
| fn visit_attribute(&mut self, attr: &'a rustc_ast::Attribute) { | |
| if attr.has_name(kw::Default) { | |
| let post = if self.cx.ecfg.features.default_field_values() { | |
| " or variants where every field has a default value" | |
| } else { | |
| "" | |
| }; | |
| self.cx.dcx().emit_err(diagnostics::NonUnitDefault { span: attr.span, post }); | |
| } | |
| rustc_ast::visit::walk_attribute(self, attr); | |
| } | |
| fn visit_variant(&mut self, v: &'a rustc_ast::Variant) { | |
| self.visit_ident(&v.ident); | |
| self.visit_vis(&v.vis); | |
| self.visit_variant_data(&v.data); | |
| visit_opt!(self, visit_anon_const, &v.disr_expr); | |
| for attr in &v.attrs { | |
| rustc_ast::visit::walk_attribute(self, attr); | |
| } | |
| } | |
| } | |
| fn has_a_default_variant(item: &Annotatable) -> bool { | |
| struct HasDefaultAttrOnVariant; | |
| impl<'ast> rustc_ast::visit::Visitor<'ast> for HasDefaultAttrOnVariant { | |
| type Result = ControlFlow<()>; | |
| fn visit_variant(&mut self, v: &'ast rustc_ast::Variant) -> ControlFlow<()> { | |
| if v.attrs.iter().any(|attr| attr.has_name(kw::Default)) { | |
| ControlFlow::Break(()) | |
| } else { | |
| // no need to walk the variant, we are only looking for top level variants | |
| ControlFlow::Continue(()) | |
| } | |
| } | |
| } | |
| item.visit_with(&mut HasDefaultAttrOnVariant).is_break() | |
| } |