]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_builtin_macros/src/deriving/default.rs
Test drop_tracking_mir before querying generator.
[rust.git] / compiler / rustc_builtin_macros / src / deriving / default.rs
1 use crate::deriving::generic::ty::*;
2 use crate::deriving::generic::*;
3 use rustc_ast as ast;
4 use rustc_ast::{walk_list, EnumDef, VariantData};
5 use rustc_errors::Applicability;
6 use rustc_expand::base::{Annotatable, DummyResult, ExtCtxt};
7 use rustc_span::symbol::Ident;
8 use rustc_span::symbol::{kw, sym};
9 use rustc_span::Span;
10 use smallvec::SmallVec;
11 use thin_vec::thin_vec;
12
13 pub fn expand_deriving_default(
14     cx: &mut ExtCtxt<'_>,
15     span: Span,
16     mitem: &ast::MetaItem,
17     item: &Annotatable,
18     push: &mut dyn FnMut(Annotatable),
19     is_const: bool,
20 ) {
21     item.visit_with(&mut DetectNonVariantDefaultAttr { cx });
22
23     let attrs = thin_vec![cx.attr_word(sym::inline, span)];
24     let trait_def = TraitDef {
25         span,
26         path: Path::new(vec![kw::Default, sym::Default]),
27         skip_path_as_bound: has_a_default_variant(item),
28         additional_bounds: Vec::new(),
29         supports_unions: false,
30         methods: vec![MethodDef {
31             name: kw::Default,
32             generics: Bounds::empty(),
33             explicit_self: false,
34             nonself_args: Vec::new(),
35             ret_ty: Self_,
36             attributes: attrs,
37             fieldless_variants_strategy: FieldlessVariantsStrategy::Default,
38             combine_substructure: combine_substructure(Box::new(|cx, trait_span, substr| {
39                 match substr.fields {
40                     StaticStruct(_, fields) => {
41                         default_struct_substructure(cx, trait_span, substr, fields)
42                     }
43                     StaticEnum(enum_def, _) => default_enum_substructure(cx, trait_span, enum_def),
44                     _ => cx.span_bug(trait_span, "method in `derive(Default)`"),
45                 }
46             })),
47         }],
48         associated_types: Vec::new(),
49         is_const,
50     };
51     trait_def.expand(cx, mitem, item, push)
52 }
53
54 fn default_struct_substructure(
55     cx: &mut ExtCtxt<'_>,
56     trait_span: Span,
57     substr: &Substructure<'_>,
58     summary: &StaticFields,
59 ) -> BlockOrExpr {
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     let default_call = |span| cx.expr_call_global(span, default_ident.clone(), Vec::new());
63
64     let expr = match summary {
65         Unnamed(_, false) => cx.expr_ident(trait_span, substr.type_ident),
66         Unnamed(fields, true) => {
67             let exprs = fields.iter().map(|sp| default_call(*sp)).collect();
68             cx.expr_call_ident(trait_span, substr.type_ident, exprs)
69         }
70         Named(fields) => {
71             let default_fields = fields
72                 .iter()
73                 .map(|&(ident, span)| cx.field_imm(span, ident, default_call(span)))
74                 .collect();
75             cx.expr_struct_ident(trait_span, substr.type_ident, default_fields)
76         }
77     };
78     BlockOrExpr::new_expr(expr)
79 }
80
81 fn default_enum_substructure(
82     cx: &mut ExtCtxt<'_>,
83     trait_span: Span,
84     enum_def: &EnumDef,
85 ) -> BlockOrExpr {
86     let expr = if let Ok(default_variant) = extract_default_variant(cx, enum_def, trait_span)
87         && let Ok(_) = validate_default_attribute(cx, default_variant)
88     {
89         // We now know there is exactly one unit variant with exactly one `#[default]` attribute.
90         cx.expr_path(cx.path(
91             default_variant.span,
92             vec![Ident::new(kw::SelfUpper, default_variant.span), default_variant.ident],
93         ))
94     } else {
95         DummyResult::raw_expr(trait_span, true)
96     };
97     BlockOrExpr::new_expr(expr)
98 }
99
100 fn extract_default_variant<'a>(
101     cx: &mut ExtCtxt<'_>,
102     enum_def: &'a EnumDef,
103     trait_span: Span,
104 ) -> Result<&'a rustc_ast::Variant, ()> {
105     let default_variants: SmallVec<[_; 1]> = enum_def
106         .variants
107         .iter()
108         .filter(|variant| cx.sess.contains_name(&variant.attrs, kw::Default))
109         .collect();
110
111     let variant = match default_variants.as_slice() {
112         [variant] => variant,
113         [] => {
114             let possible_defaults = enum_def
115                 .variants
116                 .iter()
117                 .filter(|variant| matches!(variant.data, VariantData::Unit(..)))
118                 .filter(|variant| !cx.sess.contains_name(&variant.attrs, sym::non_exhaustive));
119
120             let mut diag = cx.struct_span_err(trait_span, "no default declared");
121             diag.help("make a unit variant default by placing `#[default]` above it");
122             for variant in possible_defaults {
123                 // Suggest making each unit variant default.
124                 diag.tool_only_span_suggestion(
125                     variant.span,
126                     &format!("make `{}` default", variant.ident),
127                     format!("#[default] {}", variant.ident),
128                     Applicability::MaybeIncorrect,
129                 );
130             }
131             diag.emit();
132
133             return Err(());
134         }
135         [first, rest @ ..] => {
136             let mut diag = cx.struct_span_err(trait_span, "multiple declared defaults");
137             diag.span_label(first.span, "first default");
138             diag.span_labels(rest.iter().map(|variant| variant.span), "additional default");
139             diag.note("only one variant can be default");
140             for variant in &default_variants {
141                 // Suggest making each variant already tagged default.
142                 let suggestion = default_variants
143                     .iter()
144                     .filter_map(|v| {
145                         if v.span == variant.span {
146                             None
147                         } else {
148                             Some((cx.sess.find_by_name(&v.attrs, kw::Default)?.span, String::new()))
149                         }
150                     })
151                     .collect();
152
153                 diag.tool_only_multipart_suggestion(
154                     &format!("make `{}` default", variant.ident),
155                     suggestion,
156                     Applicability::MaybeIncorrect,
157                 );
158             }
159             diag.emit();
160
161             return Err(());
162         }
163     };
164
165     if !matches!(variant.data, VariantData::Unit(..)) {
166         cx.struct_span_err(
167             variant.ident.span,
168             "the `#[default]` attribute may only be used on unit enum variants",
169         )
170         .help("consider a manual implementation of `Default`")
171         .emit();
172
173         return Err(());
174     }
175
176     if let Some(non_exhaustive_attr) = cx.sess.find_by_name(&variant.attrs, sym::non_exhaustive) {
177         cx.struct_span_err(variant.ident.span, "default variant must be exhaustive")
178             .span_label(non_exhaustive_attr.span, "declared `#[non_exhaustive]` here")
179             .help("consider a manual implementation of `Default`")
180             .emit();
181
182         return Err(());
183     }
184
185     Ok(variant)
186 }
187
188 fn validate_default_attribute(
189     cx: &mut ExtCtxt<'_>,
190     default_variant: &rustc_ast::Variant,
191 ) -> Result<(), ()> {
192     let attrs: SmallVec<[_; 1]> =
193         cx.sess.filter_by_name(&default_variant.attrs, kw::Default).collect();
194
195     let attr = match attrs.as_slice() {
196         [attr] => attr,
197         [] => cx.bug(
198             "this method must only be called with a variant that has a `#[default]` attribute",
199         ),
200         [first, rest @ ..] => {
201             let suggestion_text =
202                 if rest.len() == 1 { "try removing this" } else { "try removing these" };
203
204             cx.struct_span_err(default_variant.ident.span, "multiple `#[default]` attributes")
205                 .note("only one `#[default]` attribute is needed")
206                 .span_label(first.span, "`#[default]` used here")
207                 .span_label(rest[0].span, "`#[default]` used again here")
208                 .span_help(rest.iter().map(|attr| attr.span).collect::<Vec<_>>(), suggestion_text)
209                 // This would otherwise display the empty replacement, hence the otherwise
210                 // repetitive `.span_help` call above.
211                 .tool_only_multipart_suggestion(
212                     suggestion_text,
213                     rest.iter().map(|attr| (attr.span, String::new())).collect(),
214                     Applicability::MachineApplicable,
215                 )
216                 .emit();
217
218             return Err(());
219         }
220     };
221     if !attr.is_word() {
222         cx.struct_span_err(attr.span, "`#[default]` attribute does not accept a value")
223             .span_suggestion_hidden(
224                 attr.span,
225                 "try using `#[default]`",
226                 "#[default]",
227                 Applicability::MaybeIncorrect,
228             )
229             .emit();
230
231         return Err(());
232     }
233     Ok(())
234 }
235
236 struct DetectNonVariantDefaultAttr<'a, 'b> {
237     cx: &'a ExtCtxt<'b>,
238 }
239
240 impl<'a, 'b> rustc_ast::visit::Visitor<'a> for DetectNonVariantDefaultAttr<'a, 'b> {
241     fn visit_attribute(&mut self, attr: &'a rustc_ast::Attribute) {
242         if attr.has_name(kw::Default) {
243             self.cx
244                 .struct_span_err(
245                     attr.span,
246                     "the `#[default]` attribute may only be used on unit enum variants",
247                 )
248                 .emit();
249         }
250
251         rustc_ast::visit::walk_attribute(self, attr);
252     }
253     fn visit_variant(&mut self, v: &'a rustc_ast::Variant) {
254         self.visit_ident(v.ident);
255         self.visit_vis(&v.vis);
256         self.visit_variant_data(&v.data);
257         walk_list!(self, visit_anon_const, &v.disr_expr);
258         for attr in &v.attrs {
259             rustc_ast::visit::walk_attribute(self, attr);
260         }
261     }
262 }
263
264 fn has_a_default_variant(item: &Annotatable) -> bool {
265     struct HasDefaultAttrOnVariant {
266         found: bool,
267     }
268
269     impl<'ast> rustc_ast::visit::Visitor<'ast> for HasDefaultAttrOnVariant {
270         fn visit_variant(&mut self, v: &'ast rustc_ast::Variant) {
271             if v.attrs.iter().any(|attr| attr.has_name(kw::Default)) {
272                 self.found = true;
273             }
274             // no need to subrecurse.
275         }
276     }
277
278     let mut visitor = HasDefaultAttrOnVariant { found: false };
279     item.visit_with(&mut visitor);
280     visitor.found
281 }