Skip to main content

rustc_builtin_macros/deriving/
debug.rs

1use rustc_ast::{self as ast, EnumDef};
2use rustc_expand::base::ExtCtxt;
3use rustc_session::config::FmtDebug;
4use rustc_span::{Ident, Span, Symbol, sym};
5use thin_vec::{ThinVec, thin_vec};
6
7use crate::deriving::generic::*;
8use crate::deriving::path_std;
9
10pub(crate) fn expand_deriving_debug(
11    cx: &ExtCtxt<'_>,
12    span: Span,
13    item: &ast::Item,
14    push: &mut dyn FnMut(Box<ast::Item>),
15    is_const: bool,
16) {
17    // &mut ::std::fmt::Formatter
18    let fmtr = cx.ty_ref(
19        span,
20        cx.ty_path(cx.std_path(span, { &[sym::fmt, sym::Formatter] })path_std!(cx, span, fmt::Formatter)),
21        None,
22        ast::Mutability::Mut,
23    );
24
25    let all_fieldless = match &item.kind {
26        ast::ItemKind::Enum(_, _, def) => {
27            def.variants.len() > 1 && def.variants.iter().all(|v| v.data.fields().is_empty())
28        }
29        _ => false,
30    };
31
32    let trait_def = TraitDef {
33        span,
34        path: cx.std_path(span, { &[sym::fmt, sym::Debug] })path_std!(cx, span, fmt::Debug),
35        needs_copy_as_bound_if_packed: true,
36        additional_bounds: SmallVec::new(),
37        supports_unions: false,
38        methods: {
    let count = 0usize + 1usize;
    let mut vec = ::smallvec::SmallVec::new();
    if count <= vec.inline_size() {
        vec.push(MethodDef {
                name: sym::fmt,
                generics: cx.empty_generics(span),
                explicit_self: true,
                nonself_args: {
                    let count = 0usize + 1usize;
                    let mut vec = ::smallvec::SmallVec::new();
                    if count <= vec.inline_size() {
                        vec.push((fmtr, sym::f));
                        vec
                    } else {
                        ::smallvec::SmallVec::from_vec(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                                    [(fmtr, sym::f)])))
                    }
                },
                has_other_selflike_arg: false,
                ret_ty: cx.ty_path(cx.std_path(span,
                        { &[sym::fmt, sym::Result] })),
                attributes: {
                    let len = [()].len();
                    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
                    vec.push(cx.generated_attr(span,
                            ast::GeneratedAttr::Inline));
                    vec
                },
                fieldless_variants_strategy: FieldlessVariantsStrategy::Default,
                combine_substructure: combine_substructure(|cx, span, substr|
                        show_substructure(cx, span, substr,
                            item.kind.ident().unwrap())),
            });
        vec
    } else {
        ::smallvec::SmallVec::from_vec(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                    [MethodDef {
                                name: sym::fmt,
                                generics: cx.empty_generics(span),
                                explicit_self: true,
                                nonself_args: {
                                    let count = 0usize + 1usize;
                                    let mut vec = ::smallvec::SmallVec::new();
                                    if count <= vec.inline_size() {
                                        vec.push((fmtr, sym::f));
                                        vec
                                    } else {
                                        ::smallvec::SmallVec::from_vec(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
                                                    [(fmtr, sym::f)])))
                                    }
                                },
                                has_other_selflike_arg: false,
                                ret_ty: cx.ty_path(cx.std_path(span,
                                        { &[sym::fmt, sym::Result] })),
                                attributes: {
                                    let len = [()].len();
                                    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
                                    vec.push(cx.generated_attr(span,
                                            ast::GeneratedAttr::Inline));
                                    vec
                                },
                                fieldless_variants_strategy: FieldlessVariantsStrategy::Default,
                                combine_substructure: combine_substructure(|cx, span,
                                        substr|
                                        show_substructure(cx, span, substr,
                                            item.kind.ident().unwrap())),
                            }])))
    }
}smallvec![MethodDef {
39            name: sym::fmt,
40            generics: cx.empty_generics(span),
41            explicit_self: true,
42            nonself_args: smallvec![(fmtr, sym::f)],
43            has_other_selflike_arg: false,
44            ret_ty: cx.ty_path(path_std!(cx, span, fmt::Result)),
45            attributes: thin_vec![cx.generated_attr(span, ast::GeneratedAttr::Inline)],
46            fieldless_variants_strategy: FieldlessVariantsStrategy::Default,
47            combine_substructure: combine_substructure(|cx, span, substr| show_substructure(
48                cx,
49                span,
50                substr,
51                item.kind.ident().unwrap()
52            )),
53        }],
54        is_const,
55        ..
56    };
57    trait_def.expand_ext(cx, item, push, all_fieldless);
58}
59
60fn show_substructure(
61    cx: &ExtCtxt<'_>,
62    span: Span,
63    substr: Substructure<'_>,
64    type_ident: Ident,
65) -> BlockOrExpr {
66    let fmt_detail = cx.sess.opts.unstable_opts.fmt_debug;
67    if fmt_detail == FmtDebug::None {
68        return BlockOrExpr::new_expr(cx.expr_ok(span, cx.expr_tuple(span, ThinVec::new())));
69    }
70
71    let (ident, vdata, fields) = match substr {
72        Struct(vdata, fields) => (type_ident, vdata, fields),
73        EnumMatching(v, fields) => (v.ident, &v.data, fields),
74        StaticEnum(enum_def) => return show_fieldless_enum(cx, span, enum_def, type_ident),
75        _ => cx.dcx().span_bug(span, "unexpected substructure in `derive(Debug)`"),
76    };
77
78    let expr_name = || cx.expr_str(span, ident.name);
79    let fmt = cx.expr_name(span, sym::f);
80
81    // Fieldless enums have been special-cased earlier
82    if fmt_detail == FmtDebug::Shallow {
83        let fn_path_write_str = cx.std_path(span, &[sym::fmt, sym::Formatter, sym::write_str]);
84        let expr = cx.expr_call_global(span, fn_path_write_str, {
    let len = [(), ()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(fmt);
    vec.push(expr_name());
    vec
}thin_vec![fmt, expr_name()]);
85        return BlockOrExpr::new_expr(expr);
86    }
87
88    // Struct and tuples are similar enough that we use the same code for both,
89    // with some extra pieces for structs due to the field names.
90    let is_struct = match vdata {
91        ast::VariantData::Unit(..) => {
92            // Special fast path for unit variants.
93            if !fields.is_empty() {
    ::core::panicking::panic("assertion failed: fields.is_empty()")
};assert!(fields.is_empty());
94            false
95        }
96        ast::VariantData::Tuple(..) => false,
97        ast::VariantData::Struct { .. } => true,
98    };
99
100    // The number of fields that can be handled without an array.
101    const CUTOFF: usize = 5;
102
103    let len = fields.len();
104    let expr_for_field = |field: FieldInfo, index: usize| -> Box<ast::Expr> {
105        if index < len - 1 {
106            field.self_expr
107        } else {
108            // Unsized types need an extra indirection, but only the last field
109            // may be unsized.
110            cx.expr_addr_of(field.span, field.self_expr)
111        }
112    };
113
114    if fields.is_empty() {
115        // Special case for no fields.
116        let fn_path_write_str = cx.std_path(span, &[sym::fmt, sym::Formatter, sym::write_str]);
117        let expr = cx.expr_call_global(span, fn_path_write_str, {
    let len = [(), ()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(fmt);
    vec.push(expr_name());
    vec
}thin_vec![fmt, expr_name()]);
118        BlockOrExpr::new_expr(expr)
119    } else if fields.len() <= CUTOFF {
120        // Few enough fields that we can use a specific-length method.
121        let debug = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("debug_struct_field{0}_finish",
                fields.len()))
    })format!("debug_struct_field{}_finish", fields.len());
122        let fn_path_debug = cx.std_path(span, &[sym::fmt, sym::Formatter, Symbol::intern(&debug)]);
123
124        let mut args = ThinVec::with_capacity(2 + fields.len());
125        args.extend([fmt]);
126
127        if is_struct {
128            let mut names = String::new();
129            names.push_str(ident.name.as_str());
130
131            for field in fields.iter() {
132                names.push(' ');
133                names.push_str(field.name.unwrap().name.as_str());
134            }
135
136            let name_expr = cx.expr_str(span, Symbol::intern(&names));
137            args.push(name_expr);
138        } else {
139            args.push(expr_name());
140        };
141
142        for (i, field) in fields.into_iter().enumerate() {
143            let field = expr_for_field(field, i);
144            args.push(field);
145        }
146        let expr = cx.expr_call_global(span, fn_path_debug, args);
147        BlockOrExpr::new_expr(expr)
148    } else {
149        // Enough fields that we must use the any-length method.
150        let mut names = String::new();
151        if is_struct {
152            names.push_str(ident.name.as_str());
153        }
154        let mut value_exprs = ThinVec::with_capacity(fields.len());
155
156        for (i, field) in fields.into_iter().enumerate() {
157            if is_struct {
158                names.push(' ');
159                names.push_str(field.name.unwrap().name.as_str());
160            }
161
162            let field = expr_for_field(field, i);
163            value_exprs.push(field);
164        }
165
166        // `let names: &'static _ = "StructName field1 field2"`
167        let names_let = is_struct.then(|| {
168            let lt_static = Some(cx.lifetime_static(span));
169            let ty_static_ref = cx.ty_ref(span, cx.ty_infer(span), lt_static, ast::Mutability::Not);
170            let names_expr = cx.expr_str(span, Symbol::intern(&names));
171            cx.stmt_let_ty(span, false, sym::names, Some(ty_static_ref), names_expr)
172        });
173
174        // `let values: &[&dyn Debug] = &[&&self.field1, &&self.field2];`
175        let path_debug = cx.std_path(span, &[sym::fmt, sym::Debug]);
176        let ty_dyn_debug = cx.ty(
177            span,
178            ast::TyKind::TraitObject(
179                {
    let len = [()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(cx.trait_bound(path_debug, false));
    vec
}thin_vec![cx.trait_bound(path_debug, false)],
180                ast::TraitObjectSyntax::Dyn,
181            ),
182        );
183        let ty_slice = cx.ty(
184            span,
185            ast::TyKind::Slice(cx.ty_ref(span, ty_dyn_debug, None, ast::Mutability::Not)),
186        );
187        let values_let = cx.stmt_let_ty(
188            span,
189            false,
190            sym::values,
191            Some(cx.ty_ref(span, ty_slice, None, ast::Mutability::Not)),
192            cx.expr_array_ref(span, value_exprs),
193        );
194
195        // `fmt::Formatter::debug_struct_fields_finish(fmt, names, values)`
196        let sym_debug = sym::debug_struct_fields_finish;
197        let fn_path_debug_internal = cx.std_path(span, &[sym::fmt, sym::Formatter, sym_debug]);
198
199        let args = {
    let len = [(), (), ()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(fmt);
    vec.push(if is_struct {
            cx.expr_name(span, sym::names)
        } else { expr_name() });
    vec.push(cx.expr_name(span, sym::values));
    vec
}thin_vec![
200            fmt,
201            if is_struct { cx.expr_name(span, sym::names) } else { expr_name() },
202            cx.expr_name(span, sym::values)
203        ];
204
205        let expr = cx.expr_call_global(span, fn_path_debug_internal, args);
206
207        let stmts = if is_struct {
208            {
    let len = [(), ()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(names_let.unwrap());
    vec.push(values_let);
    vec
}thin_vec![names_let.unwrap(), values_let]
209        } else {
210            {
    let len = [()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(values_let);
    vec
}thin_vec![values_let]
211        };
212
213        BlockOrExpr::new_mixed(stmts, Some(expr))
214    }
215}
216
217/// Special case for enums with no fields. Builds:
218/// ```text
219/// impl ::core::fmt::Debug for A {
220///     fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
221///          ::core::fmt::Formatter::write_str(f,
222///             match self {
223///                 A::A => "A",
224///                 A::B() => "B",
225///                 A::C {} => "C",
226///             })
227///     }
228/// }
229/// ```
230fn show_fieldless_enum(
231    cx: &ExtCtxt<'_>,
232    span: Span,
233    def: &EnumDef,
234    type_ident: Ident,
235) -> BlockOrExpr {
236    if let Some(expr) = show_fieldless_enum_concat_str(cx, span, def, type_ident) {
237        return BlockOrExpr::new_expr(expr);
238    }
239    let fmt = cx.expr_name(span, sym::f);
240    let arms = def
241        .variants
242        .iter()
243        .map(|v| {
244            let variant_path = cx.path(span, &[type_ident, v.ident]);
245            let pat = match &v.data {
246                ast::VariantData::Tuple(fields, _) => {
247                    if true {
    if !fields.is_empty() {
        ::core::panicking::panic("assertion failed: fields.is_empty()")
    };
};debug_assert!(fields.is_empty());
248                    cx.pat_tuple_struct(span, variant_path, ThinVec::new())
249                }
250                ast::VariantData::Struct { fields, .. } => {
251                    if true {
    if !fields.is_empty() {
        ::core::panicking::panic("assertion failed: fields.is_empty()")
    };
};debug_assert!(fields.is_empty());
252                    cx.pat_struct(span, variant_path, ThinVec::new())
253                }
254                ast::VariantData::Unit(_) => cx.pat_path(span, variant_path),
255            };
256            cx.arm(span, pat, cx.expr_str(span, v.ident.name))
257        })
258        .collect::<ThinVec<_>>();
259    let name = cx.expr_match(span, cx.expr_self(span), arms);
260    let fn_path_write_str = cx.std_path(span, &[sym::fmt, sym::Formatter, sym::write_str]);
261    BlockOrExpr::new_expr(cx.expr_call_global(span, fn_path_write_str, {
    let len = [(), ()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(fmt);
    vec.push(name);
    vec
}thin_vec![fmt, name]))
262}
263
264/// Special case for large enums with no fields. Builds:
265/// ```text
266/// impl ::core::fmt::Debug for A {
267///     fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
268///         ::core::fmt::Formatter::debug_c_like_enum_write_str(f,
269///             "A B C D E F",
270///             match self {
271///                 A::A => 0usize,
272///                 A::B => 2usize,
273///                 A::C => 4usize,
274///                 A::D => 6usize,
275///                 A::E => 8usize,
276///                 A::F => 10usize,
277///             })
278///     }
279/// }
280/// ```
281fn show_fieldless_enum_concat_str(
282    cx: &ExtCtxt<'_>,
283    span: Span,
284    def: &EnumDef,
285    type_ident: Ident,
286) -> Option<Box<ast::Expr>> {
287    // Minimum variant count where this optimization starts to pay off.
288    // Initial value of 10 came from https://github.com/rust-lang/rust/pull/155452.
289    // Refined with the new impl in https://github.com/rust-lang/rust/pull/162535.
290    // Tested with thresholds 10,5,2 on rustc-perf
291    // and by comparing MIR sizes for various enums in GodBolt.
292    //
293    // `match` with strings generates:
294    //  - a local for each arm
295    //  - a basic block with 5 statements for each arm,
296    //
297    // `match` with integers to index a concatenated string generates
298    //  - a single local for the string
299    //  - a single local for the index
300    //  - a basic block with 2 statements for each arm
301    //
302    // This reduces borrowck costs on larger enums.
303    const THRESHOLD: usize = 5;
304    let variants_count = def.variants.len();
305    if variants_count < THRESHOLD {
306        return None;
307    }
308    let fmt = cx.expr_name(span, sym::f);
309
310    let mut concatenated_names = String::new();
311
312    let arms = def
313        .variants
314        .iter()
315        .enumerate()
316        .map(|(i, v)| {
317            let name_offset = concatenated_names.len();
318
319            let name = v.ident.name.as_str();
320            concatenated_names.push_str(name);
321
322            let is_last = i == def.variants.len() - 1;
323            if !is_last {
324                concatenated_names.push(' ');
325            }
326
327            let variant_path = cx.path(span, &[type_ident, v.ident]);
328            let pat = match &v.data {
329                ast::VariantData::Tuple(fields, _) => {
330                    if true {
    if !fields.is_empty() {
        ::core::panicking::panic("assertion failed: fields.is_empty()")
    };
};debug_assert!(fields.is_empty());
331                    cx.pat_tuple_struct(span, variant_path, ThinVec::new())
332                }
333                ast::VariantData::Struct { fields, .. } => {
334                    if true {
    if !fields.is_empty() {
        ::core::panicking::panic("assertion failed: fields.is_empty()")
    };
};debug_assert!(fields.is_empty());
335                    cx.pat_struct(span, variant_path, ThinVec::new())
336                }
337                ast::VariantData::Unit(_) => cx.pat_path(span, variant_path),
338            };
339
340            cx.arm(span, pat, cx.expr_usize(span, name_offset))
341        })
342        .collect::<ThinVec<_>>();
343
344    let names_str_body = cx.expr_str(span, Symbol::intern(&concatenated_names));
345
346    let variant_index_expr = cx.expr_match(span, cx.expr_self(span), arms);
347
348    let fn_path = cx.std_path(span, &[sym::fmt, sym::Formatter, sym::debug_c_like_enum_write_str]);
349    let call_expr =
350        cx.expr_call_global(span, fn_path, {
    let len = [(), (), ()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(fmt);
    vec.push(names_str_body);
    vec.push(variant_index_expr);
    vec
}thin_vec![fmt, names_str_body, variant_index_expr]);
351
352    Some(call_expr)
353}