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