Skip to main content

rustc_builtin_macros/deriving/
debug.rs

1use rustc_ast::{self as ast, EnumDef, Safety};
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(new_path(cx, span, { &[sym::fmt, sym::Formatter] }, ::alloc::vec::Vec::new())path_std!(cx, span, fmt::Formatter)),
21        None,
22        ast::Mutability::Mut,
23    );
24
25    let trait_def = TraitDef {
26        span,
27        path: new_path(cx, span, { &[sym::fmt, sym::Debug] }, ::alloc::vec::Vec::new())path_std!(cx, span, fmt::Debug),
28        skip_path_as_bound: false,
29        needs_copy_as_bound_if_packed: true,
30        additional_bounds: SmallVec::new(),
31        supports_unions: false,
32        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::character('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::character('f'))])))
                    }
                },
                has_other_selflike_arg: false,
                ret_ty: cx.ty_path(new_path(cx, span,
                        { &[sym::fmt, sym::Result] }, ::alloc::vec::Vec::new())),
                attributes: {
                    let len = [()].len();
                    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
                    vec.push(cx.attr_word(sym::inline, span));
                    vec
                },
                fieldless_variants_strategy: FieldlessVariantsStrategy::SpecializeIfAllVariantsFieldless,
                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::character('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::character('f'))])))
                                    }
                                },
                                has_other_selflike_arg: false,
                                ret_ty: cx.ty_path(new_path(cx, span,
                                        { &[sym::fmt, sym::Result] }, ::alloc::vec::Vec::new())),
                                attributes: {
                                    let len = [()].len();
                                    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
                                    vec.push(cx.attr_word(sym::inline, span));
                                    vec
                                },
                                fieldless_variants_strategy: FieldlessVariantsStrategy::SpecializeIfAllVariantsFieldless,
                                combine_substructure: combine_substructure(|cx, span,
                                        substr|
                                        show_substructure(cx, span, substr,
                                            item.kind.ident().unwrap())),
                            }])))
    }
}smallvec![MethodDef {
33            name: sym::fmt,
34            generics: cx.empty_generics(span),
35            explicit_self: true,
36            nonself_args: smallvec![(fmtr, sym::character('f'))],
37            has_other_selflike_arg: false,
38            ret_ty: cx.ty_path(path_std!(cx, span, fmt::Result)),
39            attributes: thin_vec![cx.attr_word(sym::inline, span)],
40            fieldless_variants_strategy:
41                FieldlessVariantsStrategy::SpecializeIfAllVariantsFieldless,
42            combine_substructure: combine_substructure(|cx, span, substr| show_substructure(
43                cx,
44                span,
45                substr,
46                item.kind.ident().unwrap()
47            )),
48        }],
49        is_const,
50        safety: Safety::Default,
51        document: true,
52    };
53    trait_def.expand(cx, item, push)
54}
55
56fn formatter_ident(cx: &ExtCtxt<'_>, span: Span) -> Box<ast::Expr> {
57    cx.expr_ident_sym(span, sym::character('f'))
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.fields {
72        Struct(vdata, fields) => (type_ident, vdata, fields),
73        EnumMatching(v, fields) => (v.ident, &v.data, fields),
74        AllFieldlessEnum(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 name = cx.expr_str(span, ident.name);
79    let fmt = formatter_ident(cx, span);
80
81    // Fieldless enums have been special-cased earlier
82    if fmt_detail == FmtDebug::Shallow {
83        let fn_path_write_str = cx.std_path(&[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(name);
    vec
}thin_vec![fmt, 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, args_per_field) = 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, 0)
95        }
96        ast::VariantData::Tuple(..) => (false, 1),
97        ast::VariantData::Struct { .. } => (true, 2),
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(&[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(name);
    vec
}thin_vec![fmt, 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 = if is_struct {
122            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("debug_struct_field{0}_finish",
                fields.len()))
    })format!("debug_struct_field{}_finish", fields.len())
123        } else {
124            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("debug_tuple_field{0}_finish",
                fields.len()))
    })format!("debug_tuple_field{}_finish", fields.len())
125        };
126        let fn_path_debug = cx.std_path(&[sym::fmt, sym::Formatter, Symbol::intern(&debug)]);
127
128        let mut args = ThinVec::with_capacity(2 + fields.len() * args_per_field);
129        args.extend([fmt, name]);
130        for (i, field) in fields.into_iter().enumerate() {
131            if is_struct {
132                let name = cx.expr_str(field.span, field.name.unwrap().name);
133                args.push(name);
134            }
135
136            let field = expr_for_field(field, i);
137            args.push(field);
138        }
139        let expr = cx.expr_call_global(span, fn_path_debug, args);
140        BlockOrExpr::new_expr(expr)
141    } else {
142        // Enough fields that we must use the any-length method.
143        let mut name_exprs = ThinVec::with_capacity(fields.len());
144        let mut value_exprs = ThinVec::with_capacity(fields.len());
145
146        for (i, field) in fields.into_iter().enumerate() {
147            if is_struct {
148                name_exprs.push(cx.expr_str(field.span, field.name.unwrap().name));
149            }
150
151            let field = expr_for_field(field, i);
152            value_exprs.push(field);
153        }
154
155        // `let names: &'static _ = &["field1", "field2"];`
156        let names_let = is_struct.then(|| {
157            let lt_static = Some(cx.lifetime_static(span));
158            let ty_static_ref = cx.ty_ref(span, cx.ty_infer(span), lt_static, ast::Mutability::Not);
159            cx.stmt_let_ty(
160                span,
161                false,
162                Ident::new(sym::names, span),
163                Some(ty_static_ref),
164                cx.expr_array_ref(span, name_exprs),
165            )
166        });
167
168        // `let values: &[&dyn Debug] = &[&&self.field1, &&self.field2];`
169        let path_debug = cx.path_global(span, cx.std_path(&[sym::fmt, sym::Debug]));
170        let ty_dyn_debug = cx.ty(
171            span,
172            ast::TyKind::TraitObject(
173                {
    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)],
174                ast::TraitObjectSyntax::Dyn,
175            ),
176        );
177        let ty_slice = cx.ty(
178            span,
179            ast::TyKind::Slice(cx.ty_ref(span, ty_dyn_debug, None, ast::Mutability::Not)),
180        );
181        let values_let = cx.stmt_let_ty(
182            span,
183            false,
184            Ident::new(sym::values, span),
185            Some(cx.ty_ref(span, ty_slice, None, ast::Mutability::Not)),
186            cx.expr_array_ref(span, value_exprs),
187        );
188
189        // `fmt::Formatter::debug_struct_fields_finish(fmt, name, names, values)` or
190        // `fmt::Formatter::debug_tuple_fields_finish(fmt, name, values)`
191        let sym_debug = if is_struct {
192            sym::debug_struct_fields_finish
193        } else {
194            sym::debug_tuple_fields_finish
195        };
196        let fn_path_debug_internal = cx.std_path(&[sym::fmt, sym::Formatter, sym_debug]);
197
198        let mut args = ThinVec::with_capacity(4);
199        args.push(fmt);
200        args.push(name);
201        if is_struct {
202            args.push(cx.expr_ident_sym(span, sym::names));
203        }
204        args.push(cx.expr_ident_sym(span, sym::values));
205        let expr = cx.expr_call_global(span, fn_path_debug_internal, args);
206
207        let mut stmts = ThinVec::with_capacity(2);
208        if is_struct {
209            stmts.push(names_let.unwrap());
210        }
211        stmts.push(values_let);
212        BlockOrExpr::new_mixed(stmts, Some(expr))
213    }
214}
215
216/// Special case for enums with no fields. Builds:
217/// ```text
218/// impl ::core::fmt::Debug for A {
219///     fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
220///          ::core::fmt::Formatter::write_str(f,
221///             match self {
222///                 A::A => "A",
223///                 A::B() => "B",
224///                 A::C {} => "C",
225///             })
226///     }
227/// }
228/// ```
229fn show_fieldless_enum(
230    cx: &ExtCtxt<'_>,
231    span: Span,
232    def: &EnumDef,
233    type_ident: Ident,
234) -> BlockOrExpr {
235    let fmt = formatter_ident(cx, span);
236    let arms = def
237        .variants
238        .iter()
239        .map(|v| {
240            let variant_path = cx.path(span, ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [type_ident, v.ident]))vec![type_ident, v.ident]);
241            let pat = match &v.data {
242                ast::VariantData::Tuple(fields, _) => {
243                    if true {
    if !fields.is_empty() {
        ::core::panicking::panic("assertion failed: fields.is_empty()")
    };
};debug_assert!(fields.is_empty());
244                    cx.pat_tuple_struct(span, variant_path, ThinVec::new())
245                }
246                ast::VariantData::Struct { fields, .. } => {
247                    if true {
    if !fields.is_empty() {
        ::core::panicking::panic("assertion failed: fields.is_empty()")
    };
};debug_assert!(fields.is_empty());
248                    cx.pat_struct(span, variant_path, ThinVec::new())
249                }
250                ast::VariantData::Unit(_) => cx.pat_path(span, variant_path),
251            };
252            cx.arm(span, pat, cx.expr_str(span, v.ident.name))
253        })
254        .collect::<ThinVec<_>>();
255    let name = cx.expr_match(span, cx.expr_self(span), arms);
256    let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
257    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]))
258}