]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_builtin_macros/src/deriving/debug.rs
Merge commit 'fdb84cbfd25908df5683f8f62388f663d9260e39' into clippyup
[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 rustc_ast::{self as ast, MetaItem};
6 use rustc_expand::base::{Annotatable, ExtCtxt};
7 use rustc_span::symbol::{sym, Ident, Symbol};
8 use rustc_span::Span;
9
10 pub fn expand_deriving_debug(
11     cx: &mut ExtCtxt<'_>,
12     span: Span,
13     mitem: &MetaItem,
14     item: &Annotatable,
15     push: &mut dyn FnMut(Annotatable),
16 ) {
17     // &mut ::std::fmt::Formatter
18     let fmtr = Ref(Box::new(Path(path_std!(fmt::Formatter))), ast::Mutability::Mut);
19
20     let trait_def = TraitDef {
21         span,
22         attributes: Vec::new(),
23         path: path_std!(fmt::Debug),
24         additional_bounds: Vec::new(),
25         generics: Bounds::empty(),
26         supports_unions: false,
27         methods: vec![MethodDef {
28             name: sym::fmt,
29             generics: Bounds::empty(),
30             explicit_self: true,
31             nonself_args: vec![(fmtr, sym::f)],
32             ret_ty: Path(path_std!(fmt::Result)),
33             attributes: Vec::new(),
34             unify_fieldless_variants: false,
35             combine_substructure: combine_substructure(Box::new(|a, b, c| {
36                 show_substructure(a, b, c)
37             })),
38         }],
39         associated_types: Vec::new(),
40     };
41     trait_def.expand(cx, mitem, item, push)
42 }
43
44 fn show_substructure(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr {
45     let (ident, vdata, fields) = match substr.fields {
46         Struct(vdata, fields) => (substr.type_ident, *vdata, fields),
47         EnumMatching(_, _, v, fields) => (v.ident, &v.data, fields),
48         EnumTag(..) | StaticStruct(..) | StaticEnum(..) => {
49             cx.span_bug(span, "nonsensical .fields in `#[derive(Debug)]`")
50         }
51     };
52
53     // We want to make sure we have the ctxt set so that we can use unstable methods
54     let span = cx.with_def_site_ctxt(span);
55     let name = cx.expr_lit(span, ast::LitKind::Str(ident.name, ast::StrStyle::Cooked));
56     let fmt = substr.nonselflike_args[0].clone();
57
58     // Struct and tuples are similar enough that we use the same code for both,
59     // with some extra pieces for structs due to the field names.
60     let (is_struct, args_per_field) = match vdata {
61         ast::VariantData::Unit(..) => {
62             // Special fast path for unit variants.
63             assert!(fields.is_empty());
64             (false, 0)
65         }
66         ast::VariantData::Tuple(..) => (false, 1),
67         ast::VariantData::Struct(..) => (true, 2),
68     };
69
70     // The number of fields that can be handled without an array.
71     const CUTOFF: usize = 5;
72
73     if fields.is_empty() {
74         // Special case for no fields.
75         let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
76         let expr = cx.expr_call_global(span, fn_path_write_str, vec![fmt, name]);
77         BlockOrExpr::new_expr(expr)
78     } else if fields.len() <= CUTOFF {
79         // Few enough fields that we can use a specific-length method.
80         let debug = if is_struct {
81             format!("debug_struct_field{}_finish", fields.len())
82         } else {
83             format!("debug_tuple_field{}_finish", fields.len())
84         };
85         let fn_path_debug = cx.std_path(&[sym::fmt, sym::Formatter, Symbol::intern(&debug)]);
86
87         let mut args = Vec::with_capacity(2 + fields.len() * args_per_field);
88         args.extend([fmt, name]);
89         for i in 0..fields.len() {
90             let field = &fields[i];
91             if is_struct {
92                 let name = cx.expr_lit(
93                     field.span,
94                     ast::LitKind::Str(field.name.unwrap().name, ast::StrStyle::Cooked),
95                 );
96                 args.push(name);
97             }
98             // Use an extra indirection to make sure this works for unsized types.
99             let field = cx.expr_addr_of(field.span, field.self_expr.clone());
100             args.push(field);
101         }
102         let expr = cx.expr_call_global(span, fn_path_debug, args);
103         BlockOrExpr::new_expr(expr)
104     } else {
105         // Enough fields that we must use the any-length method.
106         let mut name_exprs = Vec::with_capacity(fields.len());
107         let mut value_exprs = Vec::with_capacity(fields.len());
108
109         for field in fields {
110             if is_struct {
111                 name_exprs.push(cx.expr_lit(
112                     field.span,
113                     ast::LitKind::Str(field.name.unwrap().name, ast::StrStyle::Cooked),
114                 ));
115             }
116
117             // Use an extra indirection to make sure this works for unsized types.
118             let field = cx.expr_addr_of(field.span, field.self_expr.clone());
119             value_exprs.push(field);
120         }
121
122         // `let names: &'static _ = &["field1", "field2"];`
123         let names_let = if is_struct {
124             let lt_static = Some(cx.lifetime_static(span));
125             let ty_static_ref =
126                 cx.ty_rptr(span, cx.ty_infer(span), lt_static, ast::Mutability::Not);
127             Some(cx.stmt_let_ty(
128                 span,
129                 false,
130                 Ident::new(sym::names, span),
131                 Some(ty_static_ref),
132                 cx.expr_array_ref(span, name_exprs),
133             ))
134         } else {
135             None
136         };
137
138         // `let values: &[&dyn Debug] = &[&&self.field1, &&self.field2];`
139         let path_debug = cx.path_global(span, cx.std_path(&[sym::fmt, sym::Debug]));
140         let ty_dyn_debug = cx.ty(
141             span,
142             ast::TyKind::TraitObject(vec![cx.trait_bound(path_debug)], ast::TraitObjectSyntax::Dyn),
143         );
144         let ty_slice = cx.ty(
145             span,
146             ast::TyKind::Slice(cx.ty_rptr(span, ty_dyn_debug, None, ast::Mutability::Not)),
147         );
148         let values_let = cx.stmt_let_ty(
149             span,
150             false,
151             Ident::new(sym::values, span),
152             Some(cx.ty_rptr(span, ty_slice, None, ast::Mutability::Not)),
153             cx.expr_array_ref(span, value_exprs),
154         );
155
156         // `fmt::Formatter::debug_struct_fields_finish(fmt, name, names, values)` or
157         // `fmt::Formatter::debug_tuple_fields_finish(fmt, name, values)`
158         let sym_debug = if is_struct {
159             sym::debug_struct_fields_finish
160         } else {
161             sym::debug_tuple_fields_finish
162         };
163         let fn_path_debug_internal = cx.std_path(&[sym::fmt, sym::Formatter, sym_debug]);
164
165         let mut args = Vec::with_capacity(4);
166         args.push(fmt);
167         args.push(name);
168         if is_struct {
169             args.push(cx.expr_ident(span, Ident::new(sym::names, span)));
170         }
171         args.push(cx.expr_ident(span, Ident::new(sym::values, span)));
172         let expr = cx.expr_call_global(span, fn_path_debug_internal, args);
173
174         let mut stmts = Vec::with_capacity(3);
175         if is_struct {
176             stmts.push(names_let.unwrap());
177         }
178         stmts.push(values_let);
179         BlockOrExpr::new_mixed(stmts, Some(expr))
180     }
181 }