Skip to main content

rustc_builtin_macros/deriving/
debug.rs

1use rustc_ast::{self as ast, EnumDef, ExprKind, MetaItem, Safety, TyKind, token};
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(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);
21
22    let trait_def = TraitDef {
23        span,
24        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),
25        skip_path_as_bound: false,
26        needs_copy_as_bound_if_packed: true,
27        additional_bounds: SmallVec::new(),
28        supports_unions: false,
29        methods: {
    let count = 0usize + 1usize;
    let mut vec = ::smallvec::SmallVec::new();
    if count <= vec.inline_size() {
        vec.push(MethodDef {
                name: sym::fmt,
                generics: Bounds::empty(),
                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: Bounds::empty(),
                                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 {
30            name: sym::fmt,
31            generics: Bounds::empty(),
32            explicit_self: true,
33            nonself_args: smallvec![(fmtr, sym::character('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(show_substructure),
39        }],
40        associated_types: SmallVec::new(),
41        is_const,
42        safety: Safety::Default,
43        document: true,
44    };
45    trait_def.expand(cx, mitem, item, push)
46}
47
48fn show_substructure(cx: &ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr {
49    // We want to make sure we have the ctxt set so that we can use unstable methods
50    let span = cx.with_def_site_ctxt(span);
51
52    let fmt_detail = cx.sess.opts.unstable_opts.fmt_debug;
53    if fmt_detail == FmtDebug::None {
54        return BlockOrExpr::new_expr(cx.expr_ok(span, cx.expr_tuple(span, ThinVec::new())));
55    }
56
57    let (ident, vdata, fields) = match substr.fields {
58        Struct(vdata, fields) => (substr.type_ident, *vdata, fields),
59        EnumMatching(v, fields) => (v.ident, &v.data, fields),
60        AllFieldlessEnum(enum_def) => return show_fieldless_enum(cx, span, enum_def, substr),
61        EnumDiscr(..) | StaticStruct(..) | StaticEnum(..) => {
62            cx.dcx().span_bug(span, "nonsensical .fields in `#[derive(Debug)]`")
63        }
64    };
65
66    let name = cx.expr_str(span, ident.name);
67    let fmt = substr.nonselflike_args[0].clone();
68
69    // Fieldless enums have been special-cased earlier
70    if fmt_detail == FmtDebug::Shallow {
71        let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
72        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]);
73        return BlockOrExpr::new_expr(expr);
74    }
75
76    // Struct and tuples are similar enough that we use the same code for both,
77    // with some extra pieces for structs due to the field names.
78    let (is_struct, args_per_field) = match vdata {
79        ast::VariantData::Unit(..) => {
80            // Special fast path for unit variants.
81            if !fields.is_empty() {
    ::core::panicking::panic("assertion failed: fields.is_empty()")
};assert!(fields.is_empty());
82            (false, 0)
83        }
84        ast::VariantData::Tuple(..) => (false, 1),
85        ast::VariantData::Struct { .. } => (true, 2),
86    };
87
88    // The number of fields that can be handled without an array.
89    const CUTOFF: usize = 5;
90
91    fn expr_for_field(
92        cx: &ExtCtxt<'_>,
93        field: &FieldInfo,
94        index: usize,
95        len: usize,
96    ) -> Box<ast::Expr> {
97        if index < len - 1 {
98            field.self_expr.clone()
99        } else {
100            // Unsized types need an extra indirection, but only the last field
101            // may be unsized.
102            cx.expr_addr_of(field.span, field.self_expr.clone())
103        }
104    }
105
106    if fields.is_empty() {
107        // Special case for no fields.
108        let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
109        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]);
110        BlockOrExpr::new_expr(expr)
111    } else if fields.len() <= CUTOFF {
112        // Few enough fields that we can use a specific-length method.
113        let debug = if is_struct {
114            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("debug_struct_field{0}_finish",
                fields.len()))
    })format!("debug_struct_field{}_finish", fields.len())
115        } else {
116            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("debug_tuple_field{0}_finish",
                fields.len()))
    })format!("debug_tuple_field{}_finish", fields.len())
117        };
118        let fn_path_debug = cx.std_path(&[sym::fmt, sym::Formatter, Symbol::intern(&debug)]);
119
120        let mut args = ThinVec::with_capacity(2 + fields.len() * args_per_field);
121        args.extend([fmt, name]);
122        for i in 0..fields.len() {
123            let field = &fields[i];
124            if is_struct {
125                let name = cx.expr_str(field.span, field.name.unwrap().name);
126                args.push(name);
127            }
128
129            let field = expr_for_field(cx, field, i, fields.len());
130            args.push(field);
131        }
132        let expr = cx.expr_call_global(span, fn_path_debug, args);
133        BlockOrExpr::new_expr(expr)
134    } else {
135        // Enough fields that we must use the any-length method.
136        let mut name_exprs = ThinVec::with_capacity(fields.len());
137        let mut value_exprs = ThinVec::with_capacity(fields.len());
138
139        for i in 0..fields.len() {
140            let field = &fields[i];
141            if is_struct {
142                name_exprs.push(cx.expr_str(field.span, field.name.unwrap().name));
143            }
144
145            let field = expr_for_field(cx, field, i, fields.len());
146            value_exprs.push(field);
147        }
148
149        // `let names: &'static _ = &["field1", "field2"];`
150        let names_let = is_struct.then(|| {
151            let lt_static = Some(cx.lifetime_static(span));
152            let ty_static_ref = cx.ty_ref(span, cx.ty_infer(span), lt_static, ast::Mutability::Not);
153            cx.stmt_let_ty(
154                span,
155                false,
156                Ident::new(sym::names, span),
157                Some(ty_static_ref),
158                cx.expr_array_ref(span, name_exprs),
159            )
160        });
161
162        // `let values: &[&dyn Debug] = &[&&self.field1, &&self.field2];`
163        let path_debug = cx.path_global(span, cx.std_path(&[sym::fmt, sym::Debug]));
164        let ty_dyn_debug = cx.ty(
165            span,
166            ast::TyKind::TraitObject(
167                {
    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)],
168                ast::TraitObjectSyntax::Dyn,
169            ),
170        );
171        let ty_slice = cx.ty(
172            span,
173            ast::TyKind::Slice(cx.ty_ref(span, ty_dyn_debug, None, ast::Mutability::Not)),
174        );
175        let values_let = cx.stmt_let_ty(
176            span,
177            false,
178            Ident::new(sym::values, span),
179            Some(cx.ty_ref(span, ty_slice, None, ast::Mutability::Not)),
180            cx.expr_array_ref(span, value_exprs),
181        );
182
183        // `fmt::Formatter::debug_struct_fields_finish(fmt, name, names, values)` or
184        // `fmt::Formatter::debug_tuple_fields_finish(fmt, name, values)`
185        let sym_debug = if is_struct {
186            sym::debug_struct_fields_finish
187        } else {
188            sym::debug_tuple_fields_finish
189        };
190        let fn_path_debug_internal = cx.std_path(&[sym::fmt, sym::Formatter, sym_debug]);
191
192        let mut args = ThinVec::with_capacity(4);
193        args.push(fmt);
194        args.push(name);
195        if is_struct {
196            args.push(cx.expr_ident(span, Ident::new(sym::names, span)));
197        }
198        args.push(cx.expr_ident(span, Ident::new(sym::values, span)));
199        let expr = cx.expr_call_global(span, fn_path_debug_internal, args);
200
201        let mut stmts = ThinVec::with_capacity(2);
202        if is_struct {
203            stmts.push(names_let.unwrap());
204        }
205        stmts.push(values_let);
206        BlockOrExpr::new_mixed(stmts, Some(expr))
207    }
208}
209
210/// Special case for enums with no fields. Builds:
211/// ```text
212/// impl ::core::fmt::Debug for A {
213///     fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
214///          ::core::fmt::Formatter::write_str(f,
215///             match self {
216///                 A::A => "A",
217///                 A::B() => "B",
218///                 A::C {} => "C",
219///             })
220///     }
221/// }
222/// ```
223fn show_fieldless_enum(
224    cx: &ExtCtxt<'_>,
225    span: Span,
226    def: &EnumDef,
227    substr: &Substructure<'_>,
228) -> BlockOrExpr {
229    let fmt = substr.nonselflike_args[0].clone();
230    if let Some((stmts, expr)) = show_fieldless_enum_concat_str(cx, span, def, fmt.clone()) {
231        return BlockOrExpr::new_mixed(stmts, Some(expr));
232    }
233    let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
234    let arms = def
235        .variants
236        .iter()
237        .map(|v| {
238            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]);
239            let pat = match &v.data {
240                ast::VariantData::Tuple(fields, _) => {
241                    if true {
    if !fields.is_empty() {
        ::core::panicking::panic("assertion failed: fields.is_empty()")
    };
};debug_assert!(fields.is_empty());
242                    cx.pat_tuple_struct(span, variant_path, ThinVec::new())
243                }
244                ast::VariantData::Struct { fields, .. } => {
245                    if true {
    if !fields.is_empty() {
        ::core::panicking::panic("assertion failed: fields.is_empty()")
    };
};debug_assert!(fields.is_empty());
246                    cx.pat_struct(span, variant_path, ThinVec::new())
247                }
248                ast::VariantData::Unit(_) => cx.pat_path(span, variant_path),
249            };
250            cx.arm(span, pat, cx.expr_str(span, v.ident.name))
251        })
252        .collect::<ThinVec<_>>();
253    let name = cx.expr_match(span, cx.expr_self(span), arms);
254    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]))
255}
256
257/// Special case for fieldless enums with no discriminants. Builds
258/// ```text
259/// impl ::core::fmt::Debug for A {
260///     fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
261///         static __NAMES: &str = "ABBBCC";
262///         static __OFFSET: [usize; 4] =[0, 1, 4, 6];
263///         let __d = ::core::intrinsics::discriminant_value(self) as usize;
264///         ::core::fmt::Formatter::debug_c_like_enums_write_str(f, __NAMES, &__OFFSET, __d)
265///     }
266/// }
267/// ```
268fn show_fieldless_enum_concat_str(
269    cx: &ExtCtxt<'_>,
270    span: Span,
271    def: &EnumDef,
272    fmt: Box<ast::Expr>,
273) -> Option<(ThinVec<ast::Stmt>, Box<ast::Expr>)> {
274    // Minimum variants count where this optimization starts to pay off.
275    // See https://github.com/rust-lang/rust/pull/155452 for more details.
276    const THRESHOLD: usize = 10;
277    let variants_count = def.variants.len();
278    if variants_count < THRESHOLD {
279        return None;
280    }
281
282    let variant_names = def
283        .variants
284        .iter()
285        .map(|v| v.disr_expr.is_none().then_some(v.ident.name.as_str()))
286        .collect::<Option<ThinVec<_>>>()?;
287
288    let total_bytes: usize = variant_names.iter().map(|n| n.len()).sum();
289    let mut concatenated_names = String::with_capacity(total_bytes);
290    let mut offset_indices = Vec::with_capacity(variant_names.len() + 1);
291    offset_indices.push(0);
292
293    for name in variant_names.iter() {
294        concatenated_names.push_str(name);
295        offset_indices.push(concatenated_names.len());
296    }
297
298    // Create the constant concatenated string
299    let names_ident = Ident::from_str_and_span("__NAMES", span);
300    let str_ty = cx.ty(
301        span,
302        TyKind::Ref(
303            None,
304            ast::MutTy {
305                ty: cx.ty(
306                    span,
307                    TyKind::Path(None, ast::Path::from_ident(Ident::new(sym::str, span))),
308                ),
309                mutbl: ast::Mutability::Not,
310            },
311        ),
312    );
313    let names_str_body = cx.expr_str(span, Symbol::intern(&concatenated_names));
314    let names_static_item =
315        cx.item_static(span, names_ident, str_ty, ast::Mutability::Not, names_str_body);
316
317    // Create the constant offset array
318    let offset_ident = Ident::from_str_and_span("__OFFSET", span);
319    let offset_index_exprs =
320        offset_indices.iter().map(|s| cx.expr_usize(span, *s)).collect::<ThinVec<_>>();
321    let starts_array_body = cx.expr_array(span, offset_index_exprs);
322    let usize_ty =
323        cx.ty(span, TyKind::Path(None, ast::Path::from_ident(Ident::new(sym::usize, span))));
324    let offset_array_len_expr = cx.anon_const(
325        span,
326        ExprKind::Lit(token::Lit::new(
327            token::LitKind::Integer,
328            Symbol::intern(&(variants_count + 1).to_string()),
329            None,
330        )),
331    );
332    let offset_static_item = cx.item_static(
333        span,
334        offset_ident,
335        cx.ty(span, TyKind::Array(usize_ty, offset_array_len_expr)),
336        ast::Mutability::Not,
337        starts_array_body,
338    );
339
340    // let __d = ::core::intrinsics::discriminant_value(self) as usize;
341    let discriminant_ident = Ident::from_str_and_span("__d", span);
342    let discriminant_intrinsic_path = cx.std_path(&[sym::intrinsics, sym::discriminant_value]);
343    let discriminant_cast_expr = cx.expr(
344        span,
345        ast::ExprKind::Cast(
346            cx.expr_call_global(span, discriminant_intrinsic_path, {
    let len = [()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(cx.expr_self(span));
    vec
}thin_vec![cx.expr_self(span)]),
347            cx.ty_path(ast::Path::from_ident(Ident::new(sym::usize, span))),
348        ),
349    );
350    let discriminant_let_stmt =
351        cx.stmt_let(span, false, discriminant_ident, discriminant_cast_expr);
352
353    // __d expression
354    let discriminant_expr = cx.expr_ident(span, discriminant_ident);
355
356    // __NAMES expression
357    let names_expr = cx.expr_ident(span, names_ident);
358
359    // &__OFFSET expression
360    let offset_ref_expr = cx.expr_addr_of(span, cx.expr_ident(span, offset_ident));
361
362    // ::core::fmt::Formatter::debug_c_like_enum_write_str(f, __NAMES, &__OFFSET, __d)
363    let fn_path = cx.std_path(&[sym::fmt, sym::Formatter, sym::debug_c_like_enum_write_str]);
364    let call_expr = cx.expr_call_global(
365        span,
366        fn_path,
367        {
    let len = [(), (), (), ()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(fmt);
    vec.push(names_expr);
    vec.push(offset_ref_expr);
    vec.push(discriminant_expr);
    vec
}thin_vec![fmt, names_expr, offset_ref_expr, discriminant_expr],
368    );
369
370    Some((
371        {
    let len = [(), (), ()].len();
    let mut vec = ::thin_vec::ThinVec::with_capacity(len);
    vec.push(cx.stmt_item(span, names_static_item));
    vec.push(cx.stmt_item(span, offset_static_item));
    vec.push(discriminant_let_stmt);
    vec
}thin_vec![
372            cx.stmt_item(span, names_static_item),
373            cx.stmt_item(span, offset_static_item),
374            discriminant_let_stmt,
375        ],
376        call_expr,
377    ))
378}