rustc_builtin_macros/deriving/
debug.rs

1use rustc_ast::{self as ast, EnumDef, MetaItem};
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    // &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: 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    };
46    trait_def.expand(cx, mitem, item, push)
47}
48
49fn show_substructure(cx: &ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr {
50    // We want to make sure we have the ctxt set so that we can use unstable methods
51    let span = cx.with_def_site_ctxt(span);
52
53    let fmt_detail = cx.sess.opts.unstable_opts.fmt_debug;
54    if fmt_detail == FmtDebug::None {
55        return BlockOrExpr::new_expr(cx.expr_ok(span, cx.expr_tuple(span, ThinVec::new())));
56    }
57
58    let (ident, vdata, fields) = match substr.fields {
59        Struct(vdata, fields) => (substr.type_ident, *vdata, fields),
60        EnumMatching(v, fields) => (v.ident, &v.data, fields),
61        AllFieldlessEnum(enum_def) => return show_fieldless_enum(cx, span, enum_def, substr),
62        EnumDiscr(..) | StaticStruct(..) | StaticEnum(..) => {
63            cx.dcx().span_bug(span, "nonsensical .fields in `#[derive(Debug)]`")
64        }
65    };
66
67    let name = cx.expr_str(span, ident.name);
68    let fmt = substr.nonselflike_args[0].clone();
69
70    // Fieldless enums have been special-cased earlier
71    if fmt_detail == FmtDebug::Shallow {
72        let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
73        let expr = cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, name]);
74        return BlockOrExpr::new_expr(expr);
75    }
76
77    // Struct and tuples are similar enough that we use the same code for both,
78    // with some extra pieces for structs due to the field names.
79    let (is_struct, args_per_field) = match vdata {
80        ast::VariantData::Unit(..) => {
81            // Special fast path for unit variants.
82            assert!(fields.is_empty());
83            (false, 0)
84        }
85        ast::VariantData::Tuple(..) => (false, 1),
86        ast::VariantData::Struct { .. } => (true, 2),
87    };
88
89    // The number of fields that can be handled without an array.
90    const CUTOFF: usize = 5;
91
92    fn expr_for_field(
93        cx: &ExtCtxt<'_>,
94        field: &FieldInfo,
95        index: usize,
96        len: usize,
97    ) -> Box<ast::Expr> {
98        if index < len - 1 {
99            field.self_expr.clone()
100        } else {
101            // Unsized types need an extra indirection, but only the last field
102            // may be unsized.
103            cx.expr_addr_of(field.span, field.self_expr.clone())
104        }
105    }
106
107    if fields.is_empty() {
108        // Special case for no fields.
109        let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
110        let expr = cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, name]);
111        BlockOrExpr::new_expr(expr)
112    } else if fields.len() <= CUTOFF {
113        // Few enough fields that we can use a specific-length method.
114        let debug = if is_struct {
115            format!("debug_struct_field{}_finish", fields.len())
116        } else {
117            format!("debug_tuple_field{}_finish", fields.len())
118        };
119        let fn_path_debug = cx.std_path(&[sym::fmt, sym::Formatter, Symbol::intern(&debug)]);
120
121        let mut args = ThinVec::with_capacity(2 + fields.len() * args_per_field);
122        args.extend([fmt, name]);
123        for i in 0..fields.len() {
124            let field = &fields[i];
125            if is_struct {
126                let name = cx.expr_str(field.span, field.name.unwrap().name);
127                args.push(name);
128            }
129
130            let field = expr_for_field(cx, field, i, fields.len());
131            args.push(field);
132        }
133        let expr = cx.expr_call_global(span, fn_path_debug, args);
134        BlockOrExpr::new_expr(expr)
135    } else {
136        // Enough fields that we must use the any-length method.
137        let mut name_exprs = ThinVec::with_capacity(fields.len());
138        let mut value_exprs = ThinVec::with_capacity(fields.len());
139
140        for i in 0..fields.len() {
141            let field = &fields[i];
142            if is_struct {
143                name_exprs.push(cx.expr_str(field.span, field.name.unwrap().name));
144            }
145
146            let field = expr_for_field(cx, field, i, fields.len());
147            value_exprs.push(field);
148        }
149
150        // `let names: &'static _ = &["field1", "field2"];`
151        let names_let = is_struct.then(|| {
152            let lt_static = Some(cx.lifetime_static(span));
153            let ty_static_ref = cx.ty_ref(span, cx.ty_infer(span), lt_static, ast::Mutability::Not);
154            cx.stmt_let_ty(
155                span,
156                false,
157                Ident::new(sym::names, span),
158                Some(ty_static_ref),
159                cx.expr_array_ref(span, name_exprs),
160            )
161        });
162
163        // `let values: &[&dyn Debug] = &[&&self.field1, &&self.field2];`
164        let path_debug = cx.path_global(span, cx.std_path(&[sym::fmt, sym::Debug]));
165        let ty_dyn_debug = cx.ty(
166            span,
167            ast::TyKind::TraitObject(
168                vec![cx.trait_bound(path_debug, false)],
169                ast::TraitObjectSyntax::Dyn,
170            ),
171        );
172        let ty_slice = cx.ty(
173            span,
174            ast::TyKind::Slice(cx.ty_ref(span, ty_dyn_debug, None, ast::Mutability::Not)),
175        );
176        let values_let = cx.stmt_let_ty(
177            span,
178            false,
179            Ident::new(sym::values, span),
180            Some(cx.ty_ref(span, ty_slice, None, ast::Mutability::Not)),
181            cx.expr_array_ref(span, value_exprs),
182        );
183
184        // `fmt::Formatter::debug_struct_fields_finish(fmt, name, names, values)` or
185        // `fmt::Formatter::debug_tuple_fields_finish(fmt, name, values)`
186        let sym_debug = if is_struct {
187            sym::debug_struct_fields_finish
188        } else {
189            sym::debug_tuple_fields_finish
190        };
191        let fn_path_debug_internal = cx.std_path(&[sym::fmt, sym::Formatter, sym_debug]);
192
193        let mut args = ThinVec::with_capacity(4);
194        args.push(fmt);
195        args.push(name);
196        if is_struct {
197            args.push(cx.expr_ident(span, Ident::new(sym::names, span)));
198        }
199        args.push(cx.expr_ident(span, Ident::new(sym::values, span)));
200        let expr = cx.expr_call_global(span, fn_path_debug_internal, args);
201
202        let mut stmts = ThinVec::with_capacity(2);
203        if is_struct {
204            stmts.push(names_let.unwrap());
205        }
206        stmts.push(values_let);
207        BlockOrExpr::new_mixed(stmts, Some(expr))
208    }
209}
210
211/// Special case for enums with no fields. Builds:
212/// ```text
213/// impl ::core::fmt::Debug for A {
214///     fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
215///          ::core::fmt::Formatter::write_str(f,
216///             match self {
217///                 A::A => "A",
218///                 A::B() => "B",
219///                 A::C {} => "C",
220///             })
221///     }
222/// }
223/// ```
224fn show_fieldless_enum(
225    cx: &ExtCtxt<'_>,
226    span: Span,
227    def: &EnumDef,
228    substr: &Substructure<'_>,
229) -> BlockOrExpr {
230    let fmt = substr.nonselflike_args[0].clone();
231    let arms = def
232        .variants
233        .iter()
234        .map(|v| {
235            let variant_path = cx.path(span, vec![substr.type_ident, v.ident]);
236            let pat = match &v.data {
237                ast::VariantData::Tuple(fields, _) => {
238                    debug_assert!(fields.is_empty());
239                    cx.pat_tuple_struct(span, variant_path, ThinVec::new())
240                }
241                ast::VariantData::Struct { fields, .. } => {
242                    debug_assert!(fields.is_empty());
243                    cx.pat_struct(span, variant_path, ThinVec::new())
244                }
245                ast::VariantData::Unit(_) => cx.pat_path(span, variant_path),
246            };
247            cx.arm(span, pat, cx.expr_str(span, v.ident.name))
248        })
249        .collect::<ThinVec<_>>();
250    let name = cx.expr_match(span, cx.expr_self(span), arms);
251    let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
252    BlockOrExpr::new_expr(cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, name]))
253}