Skip to main content

rustc_builtin_macros/deriving/
default.rs

1use core::ops::ControlFlow;
2
3use rustc_ast::visit::visit_opt;
4use rustc_ast::{self as ast, EnumDef, Safety, VariantData, attr};
5use rustc_expand::base::{Annotatable, DummyResult, ExtCtxt};
6use rustc_span::{ErrorGuaranteed, Ident, Span, kw, sym};
7use smallvec::SmallVec;
8use thin_vec::{ThinVec, thin_vec};
9
10use crate::deriving::generic::ty::*;
11use crate::deriving::generic::*;
12use crate::diagnostics;
13
14pub(crate) fn expand_deriving_default(
15    cx: &ExtCtxt<'_>,
16    span: Span,
17    mitem: &ast::MetaItem,
18    item: &Annotatable,
19    push: &mut dyn FnMut(Annotatable),
20    is_const: bool,
21) {
22    item.visit_with(&mut DetectNonVariantDefaultAttr { cx });
23
24    let trait_def = TraitDef {
25        span,
26        path: Path::new(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [kw::Default, sym::Default]))vec![kw::Default, sym::Default]),
27        skip_path_as_bound: has_a_default_variant(item),
28        needs_copy_as_bound_if_packed: false,
29        additional_bounds: SmallVec::new(),
30        supports_unions: false,
31        methods: {
    let count = 0usize + 1usize;
    let mut vec = ::smallvec::SmallVec::new();
    if count <= vec.inline_size() {
        vec.push(MethodDef {
                name: kw::Default,
                generics: Bounds::empty(),
                explicit_self: false,
                nonself_args: SmallVec::new(),
                ret_ty: Self_,
                attributes: {
                    let len = [()].len();
                    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
                    vec.push(cx.attr_word(sym::inline, span));
                    vec
                },
                fieldless_variants_strategy: FieldlessVariantsStrategy::Default,
                combine_substructure: combine_substructure(|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)`"),
                            }
                        }),
            });
        vec
    } else {
        ::smallvec::SmallVec::from_vec(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                    [MethodDef {
                                name: kw::Default,
                                generics: Bounds::empty(),
                                explicit_self: false,
                                nonself_args: SmallVec::new(),
                                ret_ty: Self_,
                                attributes: {
                                    let len = [()].len();
                                    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
                                    vec.push(cx.attr_word(sym::inline, span));
                                    vec
                                },
                                fieldless_variants_strategy: FieldlessVariantsStrategy::Default,
                                combine_substructure: combine_substructure(|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)`"),
                                            }
                                        }),
                            }])))
    }
}smallvec![MethodDef {
32            name: kw::Default,
33            generics: Bounds::empty(),
34            explicit_self: false,
35            nonself_args: SmallVec::new(),
36            ret_ty: Self_,
37            attributes: thin_vec![cx.attr_word(sym::inline, span)],
38            fieldless_variants_strategy: FieldlessVariantsStrategy::Default,
39            combine_substructure: combine_substructure(|cx, trait_span, substr| {
40                match substr.fields {
41                    StaticStruct(_, fields) => {
42                        default_struct_substructure(cx, trait_span, substr, fields)
43                    }
44                    StaticEnum(enum_def) => {
45                        default_enum_substructure(cx, trait_span, enum_def, item.span())
46                    }
47                    _ => cx.dcx().span_bug(trait_span, "method in `derive(Default)`"),
48                }
49            }),
50        }],
51        associated_types: SmallVec::new(),
52        is_const,
53        safety: Safety::Default,
54        document: true,
55    };
56    trait_def.expand(cx, mitem, item, push)
57}
58
59fn default_call(cx: &ExtCtxt<'_>, span: Span) -> Box<ast::Expr> {
60    // Note that `kw::Default` is "default" and `sym::Default` is "Default"!
61    let default_ident = cx.std_path(&[kw::Default, sym::Default, kw::Default]);
62    cx.expr_call_global(span, default_ident, ThinVec::new())
63}
64
65fn default_struct_substructure(
66    cx: &ExtCtxt<'_>,
67    trait_span: Span,
68    substr: &Substructure<'_>,
69    summary: &StaticFields<'_>,
70) -> BlockOrExpr {
71    let expr = match summary {
72        Unnamed(_, IsTuple::No) => cx.expr_ident(trait_span, substr.type_ident),
73        Unnamed(fields, IsTuple::Yes) => {
74            let exprs = fields.iter().map(|sp| default_call(cx, *sp)).collect();
75            cx.expr_call_ident(trait_span, substr.type_ident, exprs)
76        }
77        Named(fields) => {
78            let default_fields = fields
79                .iter()
80                .map(|&(ident, span, default_val)| {
81                    let value = match default_val {
82                        // We use `Default::default()`.
83                        None => default_call(cx, span),
84                        // We use the field default const expression.
85                        Some(val) => {
86                            cx.expr(val.value.span, ast::ExprKind::ConstBlock(val.clone()))
87                        }
88                    };
89                    cx.field_imm(span, ident, value)
90                })
91                .collect();
92            cx.expr_struct_ident(trait_span, substr.type_ident, default_fields)
93        }
94    };
95    BlockOrExpr::new_expr(expr)
96}
97
98fn default_enum_substructure(
99    cx: &ExtCtxt<'_>,
100    trait_span: Span,
101    enum_def: &EnumDef,
102    item_span: Span,
103) -> BlockOrExpr {
104    let expr = match try {
105        let default_variant = extract_default_variant(cx, enum_def, trait_span, item_span)?;
106        validate_default_attribute(cx, default_variant)?;
107        default_variant
108    } {
109        Ok(default_variant) => {
110            // We now know there is exactly one unit variant with exactly one `#[default]` attribute.
111            match &default_variant.data {
112                VariantData::Unit(_) => cx.expr_path(cx.path(
113                    default_variant.span,
114                    ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Ident::new(kw::SelfUpper, default_variant.span),
                default_variant.ident]))vec![Ident::new(kw::SelfUpper, default_variant.span), default_variant.ident],
115                )),
116                VariantData::Struct { fields, .. } => {
117                    // This only happens if `#![feature(default_field_values)]`. We have validated
118                    // all fields have default values in the definition.
119                    let default_fields = fields
120                        .iter()
121                        .map(|field| {
122                            cx.field_imm(
123                                field.span,
124                                field.ident.unwrap(),
125                                match field.default_value() {
126                                    // We use `Default::default()`.
127                                    None => default_call(cx, field.span),
128                                    // We use the field default const expression.
129                                    Some(val) => cx.expr(
130                                        val.value.span,
131                                        ast::ExprKind::ConstBlock(val.clone()),
132                                    ),
133                                },
134                            )
135                        })
136                        .collect();
137                    let path = cx.path(
138                        default_variant.span,
139                        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [Ident::new(kw::SelfUpper, default_variant.span),
                default_variant.ident]))vec![
140                            Ident::new(kw::SelfUpper, default_variant.span),
141                            default_variant.ident,
142                        ],
143                    );
144                    cx.expr_struct(default_variant.span, path, default_fields)
145                }
146                // Logic error in `extract_default_variant`.
147                VariantData::Tuple(..) => {
148                    cx.dcx().bug("encountered tuple variant annotated with `#[default]`")
149                }
150            }
151        }
152        Err(guar) => DummyResult::raw_expr(trait_span, Some(guar)),
153    };
154    BlockOrExpr::new_expr(expr)
155}
156
157fn extract_default_variant<'a>(
158    cx: &ExtCtxt<'_>,
159    enum_def: &'a EnumDef,
160    trait_span: Span,
161    item_span: Span,
162) -> Result<&'a rustc_ast::Variant, ErrorGuaranteed> {
163    let default_variants: SmallVec<[_; 1]> = enum_def
164        .variants
165        .iter()
166        .filter(|variant| attr::contains_name(&variant.attrs, kw::Default))
167        .collect();
168
169    let variant = match default_variants.as_slice() {
170        [variant] => variant,
171        [] => {
172            let possible_defaults = enum_def
173                .variants
174                .iter()
175                .filter(|variant| #[allow(non_exhaustive_omitted_patterns)] match variant.data {
    VariantData::Unit(..) => true,
    _ => false,
}matches!(variant.data, VariantData::Unit(..)))
176                .filter(|variant| !attr::contains_name(&variant.attrs, sym::non_exhaustive));
177
178            let suggs = possible_defaults
179                .map(|v| diagnostics::NoDefaultVariantSugg { span: v.span.shrink_to_lo() })
180                .collect();
181            let guar = cx.dcx().emit_err(diagnostics::NoDefaultVariant {
182                span: trait_span,
183                item_span,
184                suggs,
185            });
186
187            return Err(guar);
188        }
189        [first, rest @ ..] => {
190            let suggs = default_variants
191                .iter()
192                .filter_map(|variant| {
193                    let keep = attr::find_by_name(&variant.attrs, kw::Default)?.span;
194                    let spans: Vec<Span> = default_variants
195                        .iter()
196                        .flat_map(|v| {
197                            attr::filter_by_name(&v.attrs, kw::Default)
198                                .filter_map(|attr| (attr.span != keep).then_some(attr.span))
199                        })
200                        .collect();
201                    (!spans.is_empty()).then_some(diagnostics::MultipleDefaultsSugg {
202                        spans,
203                        ident: variant.ident,
204                    })
205                })
206                .collect();
207            let guar = cx.dcx().emit_err(diagnostics::MultipleDefaults {
208                span: trait_span,
209                first: first.span,
210                additional: rest.iter().map(|v| v.span).collect(),
211                suggs,
212            });
213            return Err(guar);
214        }
215    };
216
217    if cx.ecfg.features.default_field_values()
218        && let VariantData::Struct { fields, .. } = &variant.data
219        && fields.iter().all(|f| f.default_value().is_some())
220        // Disallow `#[default] Variant {}`
221        && !fields.is_empty()
222    {
223        // Allowed
224    } else if !#[allow(non_exhaustive_omitted_patterns)] match variant.data {
    VariantData::Unit(..) => true,
    _ => false,
}matches!(variant.data, VariantData::Unit(..)) {
225        let post = if cx.ecfg.features.default_field_values() {
226            " or variants where every field has a default value"
227        } else {
228            ""
229        };
230        let guar =
231            cx.dcx().emit_err(diagnostics::NonUnitDefault { span: variant.ident.span, post });
232        return Err(guar);
233    }
234
235    if let Some(non_exhaustive_attr) = attr::find_by_name(&variant.attrs, sym::non_exhaustive) {
236        let guar = cx.dcx().emit_err(diagnostics::NonExhaustiveDefault {
237            span: variant.ident.span,
238            non_exhaustive: non_exhaustive_attr.span,
239        });
240
241        return Err(guar);
242    }
243
244    Ok(variant)
245}
246
247fn validate_default_attribute(
248    cx: &ExtCtxt<'_>,
249    default_variant: &rustc_ast::Variant,
250) -> Result<(), ErrorGuaranteed> {
251    let attrs: SmallVec<[_; 1]> =
252        attr::filter_by_name(&default_variant.attrs, kw::Default).collect();
253
254    let attr = match attrs.as_slice() {
255        [attr] => attr,
256        [] => cx.dcx().bug(
257            "this method must only be called with a variant that has a `#[default]` attribute",
258        ),
259        [first, rest @ ..] => {
260            let sugg = diagnostics::MultipleDefaultAttrsSugg {
261                spans: rest.iter().map(|attr| attr.span).collect(),
262            };
263            let guar = cx.dcx().emit_err(diagnostics::MultipleDefaultAttrs {
264                span: default_variant.ident.span,
265                first: first.span,
266                first_rest: rest[0].span,
267                rest: rest.iter().map(|attr| attr.span).collect::<Vec<_>>().into(),
268                only_one: rest.len() == 1,
269                sugg,
270            });
271
272            return Err(guar);
273        }
274    };
275    if !attr.is_word() {
276        let guar = cx.dcx().emit_err(diagnostics::DefaultHasArg { span: attr.span });
277
278        return Err(guar);
279    }
280    Ok(())
281}
282
283struct DetectNonVariantDefaultAttr<'a, 'b> {
284    cx: &'a ExtCtxt<'b>,
285}
286
287impl<'a, 'b> rustc_ast::visit::Visitor<'a> for DetectNonVariantDefaultAttr<'a, 'b> {
288    fn visit_attribute(&mut self, attr: &'a rustc_ast::Attribute) {
289        if attr.has_name(kw::Default) {
290            let post = if self.cx.ecfg.features.default_field_values() {
291                " or variants where every field has a default value"
292            } else {
293                ""
294            };
295            self.cx.dcx().emit_err(diagnostics::NonUnitDefault { span: attr.span, post });
296        }
297
298        rustc_ast::visit::walk_attribute(self, attr);
299    }
300    fn visit_variant(&mut self, v: &'a rustc_ast::Variant) {
301        self.visit_ident(&v.ident);
302        self.visit_vis(&v.vis);
303        self.visit_variant_data(&v.data);
304        if let Some(x) = &v.disr_expr {
    match ::rustc_ast_ir::visit::VisitorResult::branch(self.visit_anon_const(x))
        {
        core::ops::ControlFlow::Continue(()) =>
            (),
            #[allow(unreachable_code)]
            core::ops::ControlFlow::Break(r) => {
            return ::rustc_ast_ir::visit::VisitorResult::from_residual(r);
        }
    };
};visit_opt!(self, visit_anon_const, &v.disr_expr);
305        for attr in &v.attrs {
306            rustc_ast::visit::walk_attribute(self, attr);
307        }
308    }
309}
310
311fn has_a_default_variant(item: &Annotatable) -> bool {
312    struct HasDefaultAttrOnVariant;
313
314    impl<'ast> rustc_ast::visit::Visitor<'ast> for HasDefaultAttrOnVariant {
315        type Result = ControlFlow<()>;
316        fn visit_variant(&mut self, v: &'ast rustc_ast::Variant) -> ControlFlow<()> {
317            if v.attrs.iter().any(|attr| attr.has_name(kw::Default)) {
318                ControlFlow::Break(())
319            } else {
320                // no need to walk the variant, we are only looking for top level variants
321                ControlFlow::Continue(())
322            }
323        }
324    }
325
326    item.visit_with(&mut HasDefaultAttrOnVariant).is_break()
327}