rustc_builtin_macros/deriving/
debug.rs1use rustc_ast::{self as ast, EnumDef, MetaItem, Safety};
2use rustc_expand::base::{Annotatable, ExtCtxt};
3use rustc_session::config::FmtDebug;
4use rustc_span::{Ident, Span, Symbol, sym};
5use thin_vec::{ThinVec, thin_vec};
6
7use crate::deriving::generic::ty::*;
8use crate::deriving::generic::*;
9use crate::deriving::path_std;
10
11pub(crate) fn expand_deriving_debug(
12 cx: &ExtCtxt<'_>,
13 span: Span,
14 mitem: &MetaItem,
15 item: &Annotatable,
16 push: &mut dyn FnMut(Annotatable),
17 is_const: bool,
18) {
19 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: thin_vec![cx.attr_word(sym::inline, span)],
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 is_staged_api_crate: cx.ecfg.features.staged_api(),
45 safety: Safety::Default,
46 document: true,
47 };
48 trait_def.expand(cx, mitem, item, push)
49}
50
51fn show_substructure(cx: &ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr {
52 let span = cx.with_def_site_ctxt(span);
54
55 let fmt_detail = cx.sess.opts.unstable_opts.fmt_debug;
56 if fmt_detail == FmtDebug::None {
57 return BlockOrExpr::new_expr(cx.expr_ok(span, cx.expr_tuple(span, ThinVec::new())));
58 }
59
60 let (ident, vdata, fields) = match substr.fields {
61 Struct(vdata, fields) => (substr.type_ident, *vdata, fields),
62 EnumMatching(v, fields) => (v.ident, &v.data, fields),
63 AllFieldlessEnum(enum_def) => return show_fieldless_enum(cx, span, enum_def, substr),
64 EnumDiscr(..) | StaticStruct(..) | StaticEnum(..) => {
65 cx.dcx().span_bug(span, "nonsensical .fields in `#[derive(Debug)]`")
66 }
67 };
68
69 let name = cx.expr_str(span, ident.name);
70 let fmt = substr.nonselflike_args[0].clone();
71
72 if fmt_detail == FmtDebug::Shallow {
74 let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
75 let expr = cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, name]);
76 return BlockOrExpr::new_expr(expr);
77 }
78
79 let (is_struct, args_per_field) = match vdata {
82 ast::VariantData::Unit(..) => {
83 assert!(fields.is_empty());
85 (false, 0)
86 }
87 ast::VariantData::Tuple(..) => (false, 1),
88 ast::VariantData::Struct { .. } => (true, 2),
89 };
90
91 const CUTOFF: usize = 5;
93
94 fn expr_for_field(
95 cx: &ExtCtxt<'_>,
96 field: &FieldInfo,
97 index: usize,
98 len: usize,
99 ) -> Box<ast::Expr> {
100 if index < len - 1 {
101 field.self_expr.clone()
102 } else {
103 cx.expr_addr_of(field.span, field.self_expr.clone())
106 }
107 }
108
109 if fields.is_empty() {
110 let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
112 let expr = cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, name]);
113 BlockOrExpr::new_expr(expr)
114 } else if fields.len() <= CUTOFF {
115 let debug = if is_struct {
117 format!("debug_struct_field{}_finish", fields.len())
118 } else {
119 format!("debug_tuple_field{}_finish", fields.len())
120 };
121 let fn_path_debug = cx.std_path(&[sym::fmt, sym::Formatter, Symbol::intern(&debug)]);
122
123 let mut args = ThinVec::with_capacity(2 + fields.len() * args_per_field);
124 args.extend([fmt, name]);
125 for i in 0..fields.len() {
126 let field = &fields[i];
127 if is_struct {
128 let name = cx.expr_str(field.span, field.name.unwrap().name);
129 args.push(name);
130 }
131
132 let field = expr_for_field(cx, field, i, fields.len());
133 args.push(field);
134 }
135 let expr = cx.expr_call_global(span, fn_path_debug, args);
136 BlockOrExpr::new_expr(expr)
137 } else {
138 let mut name_exprs = ThinVec::with_capacity(fields.len());
140 let mut value_exprs = ThinVec::with_capacity(fields.len());
141
142 for i in 0..fields.len() {
143 let field = &fields[i];
144 if is_struct {
145 name_exprs.push(cx.expr_str(field.span, field.name.unwrap().name));
146 }
147
148 let field = expr_for_field(cx, field, i, fields.len());
149 value_exprs.push(field);
150 }
151
152 let names_let = is_struct.then(|| {
154 let lt_static = Some(cx.lifetime_static(span));
155 let ty_static_ref = cx.ty_ref(span, cx.ty_infer(span), lt_static, ast::Mutability::Not);
156 cx.stmt_let_ty(
157 span,
158 false,
159 Ident::new(sym::names, span),
160 Some(ty_static_ref),
161 cx.expr_array_ref(span, name_exprs),
162 )
163 });
164
165 let path_debug = cx.path_global(span, cx.std_path(&[sym::fmt, sym::Debug]));
167 let ty_dyn_debug = cx.ty(
168 span,
169 ast::TyKind::TraitObject(
170 vec![cx.trait_bound(path_debug, false)],
171 ast::TraitObjectSyntax::Dyn,
172 ),
173 );
174 let ty_slice = cx.ty(
175 span,
176 ast::TyKind::Slice(cx.ty_ref(span, ty_dyn_debug, None, ast::Mutability::Not)),
177 );
178 let values_let = cx.stmt_let_ty(
179 span,
180 false,
181 Ident::new(sym::values, span),
182 Some(cx.ty_ref(span, ty_slice, None, ast::Mutability::Not)),
183 cx.expr_array_ref(span, value_exprs),
184 );
185
186 let sym_debug = if is_struct {
189 sym::debug_struct_fields_finish
190 } else {
191 sym::debug_tuple_fields_finish
192 };
193 let fn_path_debug_internal = cx.std_path(&[sym::fmt, sym::Formatter, sym_debug]);
194
195 let mut args = ThinVec::with_capacity(4);
196 args.push(fmt);
197 args.push(name);
198 if is_struct {
199 args.push(cx.expr_ident(span, Ident::new(sym::names, span)));
200 }
201 args.push(cx.expr_ident(span, Ident::new(sym::values, span)));
202 let expr = cx.expr_call_global(span, fn_path_debug_internal, args);
203
204 let mut stmts = ThinVec::with_capacity(2);
205 if is_struct {
206 stmts.push(names_let.unwrap());
207 }
208 stmts.push(values_let);
209 BlockOrExpr::new_mixed(stmts, Some(expr))
210 }
211}
212
213fn show_fieldless_enum(
227 cx: &ExtCtxt<'_>,
228 span: Span,
229 def: &EnumDef,
230 substr: &Substructure<'_>,
231) -> BlockOrExpr {
232 let fmt = substr.nonselflike_args[0].clone();
233 let arms = def
234 .variants
235 .iter()
236 .map(|v| {
237 let variant_path = cx.path(span, vec![substr.type_ident, v.ident]);
238 let pat = match &v.data {
239 ast::VariantData::Tuple(fields, _) => {
240 debug_assert!(fields.is_empty());
241 cx.pat_tuple_struct(span, variant_path, ThinVec::new())
242 }
243 ast::VariantData::Struct { fields, .. } => {
244 debug_assert!(fields.is_empty());
245 cx.pat_struct(span, variant_path, ThinVec::new())
246 }
247 ast::VariantData::Unit(_) => cx.pat_path(span, variant_path),
248 };
249 cx.arm(span, pat, cx.expr_str(span, v.ident.name))
250 })
251 .collect::<ThinVec<_>>();
252 let name = cx.expr_match(span, cx.expr_self(span), arms);
253 let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
254 BlockOrExpr::new_expr(cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, name]))
255}