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