rustc_builtin_macros/deriving/
debug.rs

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