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