Skip to main content

rustc_builtin_macros/deriving/
default.rs

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