rustc_builtin_macros/deriving/
default.rs1use 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: Vec::new(),
30 supports_unions: false,
31 methods: ::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: Vec::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(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)`"),
}
})),
}]))vec![MethodDef {
32 name: kw::Default,
33 generics: Bounds::empty(),
34 explicit_self: false,
35 nonself_args: Vec::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(Box::new(|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: Vec::new(),
52 is_const,
53 is_staged_api_crate: cx.ecfg.features.staged_api(),
54 safety: Safety::Default,
55 document: true,
56 };
57 trait_def.expand(cx, mitem, item, push)
58}
59
60fn default_call(cx: &ExtCtxt<'_>, span: Span) -> Box<ast::Expr> {
61 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 summary: &StaticFields<'_>,
71) -> BlockOrExpr {
72 let expr = match summary {
73 Unnamed(_, IsTuple::No) => cx.expr_ident(trait_span, substr.type_ident),
74 Unnamed(fields, IsTuple::Yes) => {
75 let exprs = fields.iter().map(|sp| default_call(cx, *sp)).collect();
76 cx.expr_call_ident(trait_span, substr.type_ident, exprs)
77 }
78 Named(fields) => {
79 let default_fields = fields
80 .iter()
81 .map(|&(ident, span, default_val)| {
82 let value = match default_val {
83 None => default_call(cx, span),
85 Some(val) => {
87 cx.expr(val.value.span, ast::ExprKind::ConstBlock(val.clone()))
88 }
89 };
90 cx.field_imm(span, ident, value)
91 })
92 .collect();
93 cx.expr_struct_ident(trait_span, substr.type_ident, default_fields)
94 }
95 };
96 BlockOrExpr::new_expr(expr)
97}
98
99fn default_enum_substructure(
100 cx: &ExtCtxt<'_>,
101 trait_span: Span,
102 enum_def: &EnumDef,
103 item_span: Span,
104) -> BlockOrExpr {
105 let expr = match try {
106 let default_variant = extract_default_variant(cx, enum_def, trait_span, item_span)?;
107 validate_default_attribute(cx, default_variant)?;
108 default_variant
109 } {
110 Ok(default_variant) => {
111 match &default_variant.data {
113 VariantData::Unit(_) => cx.expr_path(cx.path(
114 default_variant.span,
115 ::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],
116 )),
117 VariantData::Struct { fields, .. } => {
118 let default_fields = fields
121 .iter()
122 .map(|field| {
123 cx.field_imm(
124 field.span,
125 field.ident.unwrap(),
126 match &field.default {
127 None => default_call(cx, field.span),
129 Some(val) => cx.expr(
131 val.value.span,
132 ast::ExprKind::ConstBlock(val.clone()),
133 ),
134 },
135 )
136 })
137 .collect();
138 let path = cx.path(
139 default_variant.span,
140 ::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![
141 Ident::new(kw::SelfUpper, default_variant.span),
142 default_variant.ident,
143 ],
144 );
145 cx.expr_struct(default_variant.span, path, default_fields)
146 }
147 VariantData::Tuple(..) => {
149 cx.dcx().bug("encountered tuple variant annotated with `#[default]`")
150 }
151 }
152 }
153 Err(guar) => DummyResult::raw_expr(trait_span, Some(guar)),
154 };
155 BlockOrExpr::new_expr(expr)
156}
157
158fn extract_default_variant<'a>(
159 cx: &ExtCtxt<'_>,
160 enum_def: &'a EnumDef,
161 trait_span: Span,
162 item_span: Span,
163) -> Result<&'a rustc_ast::Variant, ErrorGuaranteed> {
164 let default_variants: SmallVec<[_; 1]> = enum_def
165 .variants
166 .iter()
167 .filter(|variant| attr::contains_name(&variant.attrs, kw::Default))
168 .collect();
169
170 let variant = match default_variants.as_slice() {
171 [variant] => variant,
172 [] => {
173 let possible_defaults = enum_def
174 .variants
175 .iter()
176 .filter(|variant| #[allow(non_exhaustive_omitted_patterns)] match variant.data {
VariantData::Unit(..) => true,
_ => false,
}matches!(variant.data, VariantData::Unit(..)))
177 .filter(|variant| !attr::contains_name(&variant.attrs, sym::non_exhaustive));
178
179 let suggs = possible_defaults
180 .map(|v| diagnostics::NoDefaultVariantSugg { span: v.span.shrink_to_lo() })
181 .collect();
182 let guar = cx.dcx().emit_err(diagnostics::NoDefaultVariant {
183 span: trait_span,
184 item_span,
185 suggs,
186 });
187
188 return Err(guar);
189 }
190 [first, rest @ ..] => {
191 let suggs = default_variants
192 .iter()
193 .filter_map(|variant| {
194 let keep = attr::find_by_name(&variant.attrs, kw::Default)?.span;
195 let spans: Vec<Span> = default_variants
196 .iter()
197 .flat_map(|v| {
198 attr::filter_by_name(&v.attrs, kw::Default)
199 .filter_map(|attr| (attr.span != keep).then_some(attr.span))
200 })
201 .collect();
202 (!spans.is_empty()).then_some(diagnostics::MultipleDefaultsSugg {
203 spans,
204 ident: variant.ident,
205 })
206 })
207 .collect();
208 let guar = cx.dcx().emit_err(diagnostics::MultipleDefaults {
209 span: trait_span,
210 first: first.span,
211 additional: rest.iter().map(|v| v.span).collect(),
212 suggs,
213 });
214 return Err(guar);
215 }
216 };
217
218 if cx.ecfg.features.default_field_values()
219 && let VariantData::Struct { fields, .. } = &variant.data
220 && fields.iter().all(|f| f.default.is_some())
221 && !fields.is_empty()
223 {
224 } else if !#[allow(non_exhaustive_omitted_patterns)] match variant.data {
VariantData::Unit(..) => true,
_ => false,
}matches!(variant.data, VariantData::Unit(..)) {
226 let post = if cx.ecfg.features.default_field_values() {
227 " or variants where every field has a default value"
228 } else {
229 ""
230 };
231 let guar =
232 cx.dcx().emit_err(diagnostics::NonUnitDefault { span: variant.ident.span, post });
233 return Err(guar);
234 }
235
236 if let Some(non_exhaustive_attr) = attr::find_by_name(&variant.attrs, sym::non_exhaustive) {
237 let guar = cx.dcx().emit_err(diagnostics::NonExhaustiveDefault {
238 span: variant.ident.span,
239 non_exhaustive: non_exhaustive_attr.span,
240 });
241
242 return Err(guar);
243 }
244
245 Ok(variant)
246}
247
248fn validate_default_attribute(
249 cx: &ExtCtxt<'_>,
250 default_variant: &rustc_ast::Variant,
251) -> Result<(), ErrorGuaranteed> {
252 let attrs: SmallVec<[_; 1]> =
253 attr::filter_by_name(&default_variant.attrs, kw::Default).collect();
254
255 let attr = match attrs.as_slice() {
256 [attr] => attr,
257 [] => cx.dcx().bug(
258 "this method must only be called with a variant that has a `#[default]` attribute",
259 ),
260 [first, rest @ ..] => {
261 let sugg = diagnostics::MultipleDefaultAttrsSugg {
262 spans: rest.iter().map(|attr| attr.span).collect(),
263 };
264 let guar = cx.dcx().emit_err(diagnostics::MultipleDefaultAttrs {
265 span: default_variant.ident.span,
266 first: first.span,
267 first_rest: rest[0].span,
268 rest: rest.iter().map(|attr| attr.span).collect::<Vec<_>>().into(),
269 only_one: rest.len() == 1,
270 sugg,
271 });
272
273 return Err(guar);
274 }
275 };
276 if !attr.is_word() {
277 let guar = cx.dcx().emit_err(diagnostics::DefaultHasArg { span: attr.span });
278
279 return Err(guar);
280 }
281 Ok(())
282}
283
284struct DetectNonVariantDefaultAttr<'a, 'b> {
285 cx: &'a ExtCtxt<'b>,
286}
287
288impl<'a, 'b> rustc_ast::visit::Visitor<'a> for DetectNonVariantDefaultAttr<'a, 'b> {
289 fn visit_attribute(&mut self, attr: &'a rustc_ast::Attribute) {
290 if attr.has_name(kw::Default) {
291 let post = if self.cx.ecfg.features.default_field_values() {
292 " or variants where every field has a default value"
293 } else {
294 ""
295 };
296 self.cx.dcx().emit_err(diagnostics::NonUnitDefault { span: attr.span, post });
297 }
298
299 rustc_ast::visit::walk_attribute(self, attr);
300 }
301 fn visit_variant(&mut self, v: &'a rustc_ast::Variant) {
302 self.visit_ident(&v.ident);
303 self.visit_vis(&v.vis);
304 self.visit_variant_data(&v.data);
305 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);
306 for attr in &v.attrs {
307 rustc_ast::visit::walk_attribute(self, attr);
308 }
309 }
310}
311
312fn has_a_default_variant(item: &Annotatable) -> bool {
313 struct HasDefaultAttrOnVariant;
314
315 impl<'ast> rustc_ast::visit::Visitor<'ast> for HasDefaultAttrOnVariant {
316 type Result = ControlFlow<()>;
317 fn visit_variant(&mut self, v: &'ast rustc_ast::Variant) -> ControlFlow<()> {
318 if v.attrs.iter().any(|attr| attr.has_name(kw::Default)) {
319 ControlFlow::Break(())
320 } else {
321 ControlFlow::Continue(())
323 }
324 }
325 }
326
327 item.visit_with(&mut HasDefaultAttrOnVariant).is_break()
328}