Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 128 additions & 7 deletions compiler/rustc_builtin_macros/src/deriving/debug.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use rustc_ast::{self as ast, EnumDef, MetaItem, Safety};
use rustc_ast::{self as ast, EnumDef, ExprKind, MetaItem, Safety, TyKind, token};
use rustc_expand::base::{Annotatable, ExtCtxt};
use rustc_session::config::FmtDebug;
use rustc_span::{Ident, Span, Symbol, sym};
Expand Down Expand Up @@ -166,15 +166,13 @@ fn show_substructure(cx: &ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) ->
let path_debug = cx.path_global(span, cx.std_path(&[sym::fmt, sym::Debug]));
let ty_dyn_debug = cx.ty(
span,
ast::TyKind::TraitObject(
TyKind::TraitObject(
vec![cx.trait_bound(path_debug, false)],
ast::TraitObjectSyntax::Dyn,
),
);
let ty_slice = cx.ty(
span,
ast::TyKind::Slice(cx.ty_ref(span, ty_dyn_debug, None, ast::Mutability::Not)),
);
let ty_slice =
cx.ty(span, TyKind::Slice(cx.ty_ref(span, ty_dyn_debug, None, ast::Mutability::Not)));
let values_let = cx.stmt_let_ty(
span,
false,
Expand Down Expand Up @@ -230,6 +228,10 @@ fn show_fieldless_enum(
substr: &Substructure<'_>,
) -> BlockOrExpr {
let fmt = substr.nonselflike_args[0].clone();
if let Some((stmts, expr)) = show_fieldless_enum_concat_str(cx, span, def, fmt.clone()) {
return BlockOrExpr::new_mixed(stmts, Some(expr));
}
let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
let arms = def
.variants
.iter()
Expand All @@ -250,6 +252,125 @@ fn show_fieldless_enum(
})
.collect::<ThinVec<_>>();
let name = cx.expr_match(span, cx.expr_self(span), arms);
let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]);
BlockOrExpr::new_expr(cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, name]))
}

/// Special case for fieldless enums with no discriminants. Builds
/// ```text
/// impl ::core::fmt::Debug for A {
/// fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
/// const __NAMES: &str = "ABBBCC";
/// const __OFFSET: [usize; 4] =[0, 1, 4, 6];
/// let __d = ::core::intrinsics::discriminant_value(self) as usize;
/// ::core::fmt::Formatter::debug_c_like_enums_write_str(f, __NAMES, &__OFFSET, __d)
/// }
/// }
/// ```
fn show_fieldless_enum_concat_str(
cx: &ExtCtxt<'_>,
span: Span,
def: &EnumDef,
fmt: Box<ast::Expr>,
) -> Option<(ThinVec<ast::Stmt>, Box<ast::Expr>)> {
let variant_count = def.variants.len();
if variant_count < 39 {
return None;
}

let variant_names = def
.variants
.iter()
.map(|v| v.disr_expr.is_none().then_some(v.ident.name.as_str()))
.collect::<Option<ThinVec<_>>>()?;
Comment on lines +280 to +284

@makai410 makai410 Apr 18, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, I think I missed a valid case, considering:

enum Uwu {
    QwQ = 0,
    AwA = 1,
}

which has explicit discriminants but is actually dense.

View changes since the review

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we can use an offset when some variants have negative discriminants while the overall variants remain dense.

I'll do a follow-up PR to implement these.


let total_bytes: usize = variant_names.iter().map(|n| n.len()).sum();
let mut concatenated_names = String::with_capacity(total_bytes);
let mut offset_indices = Vec::with_capacity(variant_names.len() + 1);
offset_indices.push(0);

for name in variant_names.iter() {
concatenated_names.push_str(name);
offset_indices.push(concatenated_names.len());
}

// Create the constant concatenated string
let names_ident = Ident::from_str_and_span("__NAMES", span);
let str_ty = cx.ty(
span,
TyKind::Ref(
None,
ast::MutTy {
ty: cx.ty(
span,
TyKind::Path(None, ast::Path::from_ident(Ident::new(sym::str, span))),
),
mutbl: ast::Mutability::Not,
},
),
);
let names_str_expr =
ast::ConstItemRhsKind::new_body(cx.expr_str(span, Symbol::intern(&concatenated_names)));
let names_const_item = cx.item_const(span, names_ident, str_ty, names_str_expr);

// Create the constant offset array
let offset_ident = Ident::from_str_and_span("__OFFSET", span);
let offset_index_exprs =
offset_indices.iter().map(|s| cx.expr_usize(span, *s)).collect::<ThinVec<_>>();
let starts_array_expr =
ast::ConstItemRhsKind::new_body(cx.expr_array(span, offset_index_exprs));
let usize_ty =
cx.ty(span, TyKind::Path(None, ast::Path::from_ident(Ident::new(sym::usize, span))));
let offset_array_len_expr = cx.anon_const(
span,
ExprKind::Lit(token::Lit::new(
token::LitKind::Integer,
Symbol::intern(&(variant_count + 1).to_string()),
None,
)),
);
let offset_const_item = cx.item_const(
span,
offset_ident,
cx.ty(span, TyKind::Array(usize_ty, offset_array_len_expr)),
starts_array_expr,
);

// let __d = ::core::intrinsics::discriminant_value(self) as usize;
let discriminant_ident = Ident::from_str_and_span("__d", span);
let discriminant_intrinsic_path = cx.std_path(&[sym::intrinsics, sym::discriminant_value]);
let discriminant_cast_expr = cx.expr(
span,
ast::ExprKind::Cast(
cx.expr_call_global(span, discriminant_intrinsic_path, thin_vec![cx.expr_self(span)]),
cx.ty_path(ast::Path::from_ident(Ident::new(sym::usize, span))),
),
);
let discriminant_let_stmt =
cx.stmt_let(span, false, discriminant_ident, discriminant_cast_expr);

// __d expression
let discriminant_expr = cx.expr_ident(span, discriminant_ident);

// __NAMES expression
let names_expr = cx.expr_ident(span, names_ident);

// &__OFFSET expression
let offset_ref_expr = cx.expr_addr_of(span, cx.expr_ident(span, offset_ident));

// ::core::fmt::Formatter::debug_c_like_enum_write_str(f, __NAMES, &__OFFSET, __d)
let fn_path = cx.std_path(&[sym::fmt, sym::Formatter, sym::debug_c_like_enum_write_str]);
let call_expr = cx.expr_call_global(
span,
fn_path,
thin_vec![fmt, names_expr, offset_ref_expr, discriminant_expr],
);

Some((
thin_vec![
cx.stmt_item(span, names_const_item),
cx.stmt_item(span, offset_const_item),
discriminant_let_stmt,
],
call_expr,
))
}
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,7 @@ symbols! {
debug_assert_macro,
debug_assert_ne_macro,
debug_assertions,
debug_c_like_enum_write_str,
debug_struct_fields_finish,
debug_tuple_fields_finish,
debugger_visualizer,
Expand Down
15 changes: 15 additions & 0 deletions library/core/src/fmt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2577,6 +2577,21 @@ impl<'a> Formatter<'a> {
builder.finish()
}

/// Shrinks `derive(Debug)` code, for faster compilation and smaller binaries.
/// For C-like enums with concatenated variant name strings.
#[doc(hidden)]
#[unstable(feature = "fmt_helpers_for_derive", issue = "none")]
pub fn debug_c_like_enum_write_str<'b>(
&'b mut self,
names: &str,
offset: &[usize],
discr: usize,
) -> Result {
let start = offset[discr];
let end = offset[discr + 1];
self.write_str(&names[start..end])
}

/// Creates a `DebugTuple` builder designed to assist with creation of
/// `fmt::Debug` implementations for tuple structs.
///
Expand Down
53 changes: 53 additions & 0 deletions tests/ui/derives/deriving-all-codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,59 @@ enum Fieldless {
C,
}

// A C-like, fieldless enum with variants of varying name lengths.
#[derive(Debug)]
enum Fieldless0 {
A,
BBB,
CC,
}


// A C-like, fieldless enum with 39 variants.
#[derive(Debug)]
enum Fieldless39 {
AAAAA,
BBBB,
CC,
DDDDDDDD,
E,
FFFFFFFFFFFFF,
GGGGGG,
Hatsune,
IIIIIII,
JJJJJJJJJ,
KKK,
LLLLLL,
Miku,
N,
OO,
P,
Q,
R,
SSSSSS,
TTT,
U,
VV,
W,
X,
Y,
ZZZZZZZZZZZZZZZZZ,
IDONT,
KNOW,
WHAT,
TO,
SAY,
HERE,
SO,
JUST,
SOME,
RANDOM,
STUFF,
GUGUGAGA,
LOL,
}

// An enum with multiple fieldless and fielded variants.
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
enum Mixed {
Expand Down
78 changes: 78 additions & 0 deletions tests/ui/derives/deriving-all-codegen.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -1239,6 +1239,84 @@ impl ::core::cmp::Ord for Fieldless {
}
}

// A C-like, fieldless enum with variants of varying name lengths.
enum Fieldless0 { A, BBB, CC, }
#[automatically_derived]
impl ::core::fmt::Debug for Fieldless0 {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
Fieldless0::A => "A",
Fieldless0::BBB => "BBB",
Fieldless0::CC => "CC",
})
}
}


// A C-like, fieldless enum with 39 variants.
enum Fieldless39 {
AAAAA,
BBBB,
CC,
DDDDDDDD,
E,
FFFFFFFFFFFFF,
GGGGGG,
Hatsune,
IIIIIII,
JJJJJJJJJ,
KKK,
LLLLLL,
Miku,
N,
OO,
P,
Q,
R,
SSSSSS,
TTT,
U,
VV,
W,
X,
Y,
ZZZZZZZZZZZZZZZZZ,
IDONT,
KNOW,
WHAT,
TO,
SAY,
HERE,
SO,
JUST,
SOME,
RANDOM,
STUFF,
GUGUGAGA,
LOL,
}
#[automatically_derived]
impl ::core::fmt::Debug for Fieldless39 {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
const __NAMES: &str =
"AAAAABBBBCCDDDDDDDDEFFFFFFFFFFFFFGGGGGGHatsuneIIIIIIIJJJJJJJJJKKKLLLLLLMikuNOOPQRSSSSSSTTTUVVWXYZZZZZZZZZZZZZZZZZIDONTKNOWWHATTOSAYHERESOJUSTSOMERANDOMSTUFFGUGUGAGALOL";
const __OFFSET: [usize; 40] =
[0usize, 5usize, 9usize, 11usize, 19usize, 20usize, 33usize,
39usize, 46usize, 53usize, 62usize, 65usize, 71usize,
75usize, 76usize, 78usize, 79usize, 80usize, 81usize,
87usize, 90usize, 91usize, 93usize, 94usize, 95usize,
96usize, 113usize, 118usize, 122usize, 126usize, 128usize,
131usize, 135usize, 137usize, 141usize, 145usize, 151usize,
156usize, 164usize, 167usize];
let __d = ::core::intrinsics::discriminant_value(self) as usize;
::core::fmt::Formatter::debug_c_like_enum_write_str(f, __NAMES,
&__OFFSET, __d)
}
}

// An enum with multiple fieldless and fielded variants.
enum Mixed {

Expand Down
Loading