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