]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_builtin_macros/src/deriving/debug.rs
Rollup merge of #107819 - clubby789:x-py-root, r=jyn514
[rust.git] / compiler / rustc_builtin_macros / src / deriving / debug.rs
1 use crate::deriving::generic::ty::*;
2 use crate::deriving::generic::*;
3 use crate::deriving::path_std;
4
5 use ast::EnumDef;
6 use rustc_ast::{self as ast, MetaItem};
7 use rustc_expand::base::{Annotatable, ExtCtxt};
8 use rustc_span::symbol::{sym, Ident, Symbol};
9 use rustc_span::Span;
10
11 pub fn expand_deriving_debug(
12     cx: &mut ExtCtxt<'_>,
13     span: Span,
14     mitem: &MetaItem,
15     item: &Annotatable,
16     push: &mut dyn FnMut(Annotatable),
17     is_const: bool,
18 ) {
19     // &mut ::std::fmt::Formatter
20     let fmtr = Ref(Box::new(Path(path_std!(fmt::Formatter))), ast::Mutability::Mut);
21
22     let trait_def = TraitDef {
23         span,
24         path: path_std!(fmt::Debug),
25         skip_path_as_bound: false,
26         needs_copy_as_bound_if_packed: true,
27         additional_bounds: Vec::new(),
28         supports_unions: false,
29         methods: vec![MethodDef {
30             name: sym::fmt,
31             generics: Bounds::empty(),
32             explicit_self: true,
33             nonself_args: vec![(fmtr, sym::f)],
34             ret_ty: Path(path_std!(fmt::Result)),
35             attributes: ast::AttrVec::new(),
36             fieldless_variants_strategy:
37                 FieldlessVariantsStrategy::SpecializeIfAllVariantsFieldless,
38             combine_substructure: combine_substructure(Box::new(|a, b, c| {
39                 show_substructure(a, b, c)
40             })),
41         }],
42         associated_types: Vec::new(),
43         is_const,
44     };
45     trait_def.expand(cx, mitem, item, push)
46 }
47
48 fn show_substructure(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr {
49     // We want to make sure we have the ctxt set so that we can use unstable methods
50     let span = cx.with_def_site_ctxt(span);
51
52     let (ident, vdata, fields) = match substr.fields {
53         Struct(vdata, fields) => (substr.type_ident, *vdata, fields),
54         EnumMatching(_, _, v, fields) => (v.ident, &v.data, fields),
55         AllFieldlessEnum(enum_def) => return show_fieldless_enum(cx, span, enum_def, substr),
56         EnumTag(..) | StaticStruct(..) | StaticEnum(..) => {
57             cx.span_bug(span, "nonsensical .fields in `#[derive(Debug)]`")
58         }
59     };
60
61     let name = cx.expr_str(span, ident.name);
62     let fmt = substr.nonselflike_args[0].clone();
63
64     // Struct and tuples are similar enough that we use the same code for both,
65     // with some extra pieces for structs due to the field names.
66     let (is_struct, args_per_field) = match vdata {
67         ast::VariantData::Unit(..) => {
68             // Special fast path for unit variants.
69             assert!(fields.is_empty());
70             (false, 0)
71         }
72         ast::VariantData::Tuple(..) => (false, 1),
73         ast::VariantData::Struct(..) => (true, 2),
74     };
75
76     // The number of fields that can be handled without an array.
77     const CUTOFF: usize = 5;
78
79     fn expr_for_field(
80         cx: &ExtCtxt<'_>,
81         field: &FieldInfo,
82         index: usize,
83         len: usize,
84     ) -> ast::ptr::P<ast::Expr> {
85         if index < len - 1 {
86             field.self_expr.clone()
87         } else {
88             // Unsized types need an extra indirection, but only the last field
89             // may be unsized.
90             cx.expr_addr_of(field.span, field.self_expr.clone())
91         }
92     }
93
94     if fields.is_empty() {
95         // Special case for no fields.
96         let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
97         let expr = cx.expr_call_global(span, fn_path_write_str, vec![fmt, name]);
98         BlockOrExpr::new_expr(expr)
99     } else if fields.len() <= CUTOFF {
100         // Few enough fields that we can use a specific-length method.
101         let debug = if is_struct {
102             format!("debug_struct_field{}_finish", fields.len())
103         } else {
104             format!("debug_tuple_field{}_finish", fields.len())
105         };
106         let fn_path_debug = cx.std_path(&[sym::fmt, sym::Formatter, Symbol::intern(&debug)]);
107
108         let mut args = Vec::with_capacity(2 + fields.len() * args_per_field);
109         args.extend([fmt, name]);
110         for i in 0..fields.len() {
111             let field = &fields[i];
112             if is_struct {
113                 let name = cx.expr_str(field.span, field.name.unwrap().name);
114                 args.push(name);
115             }
116
117             let field = expr_for_field(cx, field, i, fields.len());
118             args.push(field);
119         }
120         let expr = cx.expr_call_global(span, fn_path_debug, args);
121         BlockOrExpr::new_expr(expr)
122     } else {
123         // Enough fields that we must use the any-length method.
124         let mut name_exprs = Vec::with_capacity(fields.len());
125         let mut value_exprs = Vec::with_capacity(fields.len());
126
127         for i in 0..fields.len() {
128             let field = &fields[i];
129             if is_struct {
130                 name_exprs.push(cx.expr_str(field.span, field.name.unwrap().name));
131             }
132
133             let field = expr_for_field(cx, field, i, fields.len());
134             value_exprs.push(field);
135         }
136
137         // `let names: &'static _ = &["field1", "field2"];`
138         let names_let = if is_struct {
139             let lt_static = Some(cx.lifetime_static(span));
140             let ty_static_ref = cx.ty_ref(span, cx.ty_infer(span), lt_static, ast::Mutability::Not);
141             Some(cx.stmt_let_ty(
142                 span,
143                 false,
144                 Ident::new(sym::names, span),
145                 Some(ty_static_ref),
146                 cx.expr_array_ref(span, name_exprs),
147             ))
148         } else {
149             None
150         };
151
152         // `let values: &[&dyn Debug] = &[&&self.field1, &&self.field2];`
153         let path_debug = cx.path_global(span, cx.std_path(&[sym::fmt, sym::Debug]));
154         let ty_dyn_debug = cx.ty(
155             span,
156             ast::TyKind::TraitObject(
157                 vec![cx.trait_bound(path_debug, false)],
158                 ast::TraitObjectSyntax::Dyn,
159             ),
160         );
161         let ty_slice = cx.ty(
162             span,
163             ast::TyKind::Slice(cx.ty_ref(span, ty_dyn_debug, None, ast::Mutability::Not)),
164         );
165         let values_let = cx.stmt_let_ty(
166             span,
167             false,
168             Ident::new(sym::values, span),
169             Some(cx.ty_ref(span, ty_slice, None, ast::Mutability::Not)),
170             cx.expr_array_ref(span, value_exprs),
171         );
172
173         // `fmt::Formatter::debug_struct_fields_finish(fmt, name, names, values)` or
174         // `fmt::Formatter::debug_tuple_fields_finish(fmt, name, values)`
175         let sym_debug = if is_struct {
176             sym::debug_struct_fields_finish
177         } else {
178             sym::debug_tuple_fields_finish
179         };
180         let fn_path_debug_internal = cx.std_path(&[sym::fmt, sym::Formatter, sym_debug]);
181
182         let mut args = Vec::with_capacity(4);
183         args.push(fmt);
184         args.push(name);
185         if is_struct {
186             args.push(cx.expr_ident(span, Ident::new(sym::names, span)));
187         }
188         args.push(cx.expr_ident(span, Ident::new(sym::values, span)));
189         let expr = cx.expr_call_global(span, fn_path_debug_internal, args);
190
191         let mut stmts = Vec::with_capacity(3);
192         if is_struct {
193             stmts.push(names_let.unwrap());
194         }
195         stmts.push(values_let);
196         BlockOrExpr::new_mixed(stmts, Some(expr))
197     }
198 }
199
200 /// Special case for enums with no fields. Builds:
201 /// ```text
202 /// impl ::core::fmt::Debug for A {
203 ///     fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
204 ///          ::core::fmt::Formatter::write_str(f,
205 ///             match self {
206 ///                 A::A => "A",
207 ///                 A::B() => "B",
208 ///                 A::C {} => "C",
209 ///             })
210 ///     }
211 /// }
212 /// ```
213 fn show_fieldless_enum(
214     cx: &mut ExtCtxt<'_>,
215     span: Span,
216     def: &EnumDef,
217     substr: &Substructure<'_>,
218 ) -> BlockOrExpr {
219     let fmt = substr.nonselflike_args[0].clone();
220     let arms = def
221         .variants
222         .iter()
223         .map(|v| {
224             let variant_path = cx.path(span, vec![substr.type_ident, v.ident]);
225             let pat = match &v.data {
226                 ast::VariantData::Tuple(fields, _) => {
227                     debug_assert!(fields.is_empty());
228                     cx.pat_tuple_struct(span, variant_path, vec![])
229                 }
230                 ast::VariantData::Struct(fields, _) => {
231                     debug_assert!(fields.is_empty());
232                     cx.pat_struct(span, variant_path, vec![])
233                 }
234                 ast::VariantData::Unit(_) => cx.pat_path(span, variant_path),
235             };
236             cx.arm(span, pat, cx.expr_str(span, v.ident.name))
237         })
238         .collect::<Vec<_>>();
239     let name = cx.expr_match(span, cx.expr_self(span), arms);
240     let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
241     BlockOrExpr::new_expr(cx.expr_call_global(span, fn_path_write_str, vec![fmt, name]))
242 }