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