From bf398d389d4974e89dc00a5974349417ddf26ccc Mon Sep 17 00:00:00 2001 From: Makai Date: Sat, 18 Apr 2026 00:28:20 +0800 Subject: [PATCH 1/2] Implement `Debug` for C-like enums with a concatenated string --- .../src/deriving/debug.rs | 156 +++++++++++++++++- tests/ui/deriving/deriving-all-codegen.rs | 8 + tests/ui/deriving/deriving-all-codegen.stdout | 27 ++- 3 files changed, 180 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index 004b13bcc333d..558846b9d37b4 100644 --- a/compiler/rustc_builtin_macros/src/deriving/debug.rs +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -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}; @@ -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, @@ -230,6 +228,14 @@ fn show_fieldless_enum( substr: &Substructure<'_>, ) -> BlockOrExpr { let fmt = substr.nonselflike_args[0].clone(); + let fn_path_write_str = cx.std_path(&[sym::fmt, sym::Formatter, sym::write_str]); + if let Some(name) = show_fieldless_enum_concat_str(cx, span, def) { + return BlockOrExpr::new_expr(cx.expr_call_global( + span, + fn_path_write_str, + thin_vec![fmt, name], + )); + } let arms = def .variants .iter() @@ -250,6 +256,142 @@ fn show_fieldless_enum( }) .collect::>(); 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])) } + +/// Specialer 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 { +/// ::core::fmt::Formatter::write_str(f, { +/// const __NAMES: &str = "ABBBCC"; +/// const __OFFSET: [usize; 4] =[0, 1, 4, 6]; +/// let __d = ::core::intrinsics::discriminant_value(self) as usize; +/// __NAMES[__OFFSET[d]..__OFFSET[d + 1]] +/// }) +/// } +/// } +/// ``` +fn show_fieldless_enum_concat_str( + cx: &ExtCtxt<'_>, + span: Span, + def: &EnumDef, +) -> Option> { + let variant_count = def.variants.len(); + if variant_count >= cx.sess.target.pointer_width as usize { + return None; + } + + let variant_names = def + .variants + .iter() + .map(|v| v.disr_expr.is_none().then_some(v.ident.name.as_str())) + .collect::>>()?; + + 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::>(); + 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); + + // __OFFSET[__d] + let discriminant_expr = cx.expr_ident(span, discriminant_ident); + let start_index_expr = cx.expr( + span, + ExprKind::Index(cx.expr_ident(span, offset_ident), discriminant_expr.clone(), span), + ); + + // __OFFSET[__d + 1] + let one_expr = cx.expr_usize(span, 1); + let discriminant_plus_one_expr = + cx.expr_binary(span, ast::BinOpKind::Add, discriminant_expr, one_expr); + let end_index_expr = cx.expr( + span, + ExprKind::Index(cx.expr_ident(span, offset_ident), discriminant_plus_one_expr, span), + ); + + // __OFFSET[__d]..__OFFSET[__d + 1] + let slice_range_expr = cx.expr( + span, + ExprKind::Range( + Some(start_index_expr), + Some(end_index_expr), + rustc_ast::RangeLimits::HalfOpen, + ), + ); + + // &__NAMES[__STARTS[__d]..__STARTS[__d + 1]] + let name_slice_expr = cx.expr_addr_of( + span, + cx.expr(span, ExprKind::Index(cx.expr_ident(span, names_ident), slice_range_expr, span)), + ); + + Some(cx.expr_block(cx.block( + span, + thin_vec![ + cx.stmt_item(span, names_const_item), + cx.stmt_item(span, offset_const_item), + discriminant_let_stmt, + cx.stmt_expr(name_slice_expr) + ], + ))) +} diff --git a/tests/ui/deriving/deriving-all-codegen.rs b/tests/ui/deriving/deriving-all-codegen.rs index 9f21831960499..d07ca4948a330 100644 --- a/tests/ui/deriving/deriving-all-codegen.rs +++ b/tests/ui/deriving/deriving-all-codegen.rs @@ -154,6 +154,14 @@ enum Fieldless { C, } +// A C-like, fieldless enum with variants of varying name lengths. +#[derive(Debug)] +enum Fieldless0 { + A, + BBB, + CC, +} + // An enum with multiple fieldless and fielded variants. #[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] enum Mixed { diff --git a/tests/ui/deriving/deriving-all-codegen.stdout b/tests/ui/deriving/deriving-all-codegen.stdout index 94e8b886436df..69dd11e173049 100644 --- a/tests/ui/deriving/deriving-all-codegen.stdout +++ b/tests/ui/deriving/deriving-all-codegen.stdout @@ -1236,10 +1236,12 @@ impl ::core::fmt::Debug for Fieldless { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { ::core::fmt::Formatter::write_str(f, - match self { - Fieldless::A => "A", - Fieldless::B => "B", - Fieldless::C => "C", + { + const __NAMES: &str = "ABC"; + const __OFFSET: [usize; 4] = [0usize, 1usize, 2usize, 3usize]; + let __d = + ::core::intrinsics::discriminant_value(self) as usize; + &__NAMES[__OFFSET[__d]..__OFFSET[__d + 1usize]] }) } } @@ -1294,6 +1296,23 @@ 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, + { + const __NAMES: &str = "ABBBCC"; + const __OFFSET: [usize; 4] = [0usize, 1usize, 4usize, 6usize]; + let __d = + ::core::intrinsics::discriminant_value(self) as usize; + &__NAMES[__OFFSET[__d]..__OFFSET[__d + 1usize]] + }) + } +} + // An enum with multiple fieldless and fielded variants. enum Mixed { From b16703bbee4b9ec6e790bbb88115c84be293635b Mon Sep 17 00:00:00 2001 From: Makai Date: Sat, 18 Apr 2026 19:14:47 +0800 Subject: [PATCH 2/2] skip bounds check in fieldless enum --- .../src/deriving/debug.rs | 71 ++++++++++++------- tests/ui/deriving/deriving-all-codegen.stdout | 12 ++-- 2 files changed, 55 insertions(+), 28 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index 558846b9d37b4..ce842b93efdb2 100644 --- a/compiler/rustc_builtin_macros/src/deriving/debug.rs +++ b/compiler/rustc_builtin_macros/src/deriving/debug.rs @@ -259,15 +259,17 @@ fn show_fieldless_enum( BlockOrExpr::new_expr(cx.expr_call_global(span, fn_path_write_str, thin_vec![fmt, name])) } -/// Specialer case for fieldless enums with no discriminants. Builds +/// 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 { -/// ::core::fmt::Formatter::write_str(f, { +/// ::core::fmt::Formatter::write_str(f, unsafe { /// const __NAMES: &str = "ABBBCC"; /// const __OFFSET: [usize; 4] =[0, 1, 4, 6]; /// let __d = ::core::intrinsics::discriminant_value(self) as usize; -/// __NAMES[__OFFSET[d]..__OFFSET[d + 1]] +/// let __start = *__OFFSET.get_unchecked(__d); +/// let __end = *__OFFSET.get_unchecked(__d + 1); +/// __NAMES.get_unchecked(__start..__end) /// }) /// } /// } @@ -278,9 +280,6 @@ fn show_fieldless_enum_concat_str( def: &EnumDef, ) -> Option> { let variant_count = def.variants.len(); - if variant_count >= cx.sess.target.pointer_width as usize { - return None; - } let variant_names = def .variants @@ -353,23 +352,40 @@ fn show_fieldless_enum_concat_str( let discriminant_let_stmt = cx.stmt_let(span, false, discriminant_ident, discriminant_cast_expr); - // __OFFSET[__d] + // __d & __d + 1 expressions let discriminant_expr = cx.expr_ident(span, discriminant_ident); - let start_index_expr = cx.expr( + let discriminant_plus_one_expr = cx.expr_binary( + span, + ast::BinOpKind::Add, + discriminant_expr.clone(), + cx.expr_usize(span, 1), + ); + + // let __start = *__OFFSET.get_unchecked(__d); + let start_ident = Ident::from_str_and_span("__start", span); + let start_index_expr = cx.expr_method_call( span, - ExprKind::Index(cx.expr_ident(span, offset_ident), discriminant_expr.clone(), span), + cx.expr_ident(span, offset_ident), + Ident::from_str_and_span("get_unchecked", span), + thin_vec![discriminant_expr], ); + let deref_start_index_expr = cx.expr_deref(span, start_index_expr); + let start_let_stmt = cx.stmt_let(span, false, start_ident, deref_start_index_expr); - // __OFFSET[__d + 1] - let one_expr = cx.expr_usize(span, 1); - let discriminant_plus_one_expr = - cx.expr_binary(span, ast::BinOpKind::Add, discriminant_expr, one_expr); - let end_index_expr = cx.expr( + // let __end = *__OFFSET.get_unchecked(__d + 1); + let end_ident = Ident::from_str_and_span("__end", span); + let end_index_expr = cx.expr_method_call( span, - ExprKind::Index(cx.expr_ident(span, offset_ident), discriminant_plus_one_expr, span), + cx.expr_ident(span, offset_ident), + Ident::from_str_and_span("get_unchecked", span), + thin_vec![discriminant_plus_one_expr], ); + let deref_end_index_expr = cx.expr_deref(span, end_index_expr); + let end_let_stmt = cx.stmt_let(span, false, end_ident, deref_end_index_expr); - // __OFFSET[__d]..__OFFSET[__d + 1] + // __start..__end + let start_index_expr = cx.expr_ident(span, start_ident); + let end_index_expr = cx.expr_ident(span, end_ident); let slice_range_expr = cx.expr( span, ExprKind::Range( @@ -379,19 +395,26 @@ fn show_fieldless_enum_concat_str( ), ); - // &__NAMES[__STARTS[__d]..__STARTS[__d + 1]] - let name_slice_expr = cx.expr_addr_of( + // __NAMES.get_unchecked(__start..__end] + let name_get_unchecked_call_expr = cx.expr_method_call( span, - cx.expr(span, ExprKind::Index(cx.expr_ident(span, names_ident), slice_range_expr, span)), + cx.expr_ident(span, names_ident), + Ident::from_str_and_span("get_unchecked", span), + thin_vec![slice_range_expr], ); - Some(cx.expr_block(cx.block( - span, - thin_vec![ + Some(cx.expr_block(Box::new(ast::Block { + stmts: thin_vec![ cx.stmt_item(span, names_const_item), cx.stmt_item(span, offset_const_item), discriminant_let_stmt, - cx.stmt_expr(name_slice_expr) + start_let_stmt, + end_let_stmt, + cx.stmt_expr(name_get_unchecked_call_expr), ], - ))) + id: ast::DUMMY_NODE_ID, + rules: ast::BlockCheckMode::Unsafe(ast::CompilerGenerated), + span, + tokens: None, + }))) } diff --git a/tests/ui/deriving/deriving-all-codegen.stdout b/tests/ui/deriving/deriving-all-codegen.stdout index 69dd11e173049..c879ae72d72ab 100644 --- a/tests/ui/deriving/deriving-all-codegen.stdout +++ b/tests/ui/deriving/deriving-all-codegen.stdout @@ -1236,12 +1236,14 @@ impl ::core::fmt::Debug for Fieldless { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { ::core::fmt::Formatter::write_str(f, - { + unsafe { const __NAMES: &str = "ABC"; const __OFFSET: [usize; 4] = [0usize, 1usize, 2usize, 3usize]; let __d = ::core::intrinsics::discriminant_value(self) as usize; - &__NAMES[__OFFSET[__d]..__OFFSET[__d + 1usize]] + let __start = *__OFFSET.get_unchecked(__d); + let __end = *__OFFSET.get_unchecked(__d + 1usize); + __NAMES.get_unchecked(__start..__end) }) } } @@ -1303,12 +1305,14 @@ impl ::core::fmt::Debug for Fieldless0 { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { ::core::fmt::Formatter::write_str(f, - { + unsafe { const __NAMES: &str = "ABBBCC"; const __OFFSET: [usize; 4] = [0usize, 1usize, 4usize, 6usize]; let __d = ::core::intrinsics::discriminant_value(self) as usize; - &__NAMES[__OFFSET[__d]..__OFFSET[__d + 1usize]] + let __start = *__OFFSET.get_unchecked(__d); + let __end = *__OFFSET.get_unchecked(__d + 1usize); + __NAMES.get_unchecked(__start..__end) }) } }