diff --git a/compiler/rustc_builtin_macros/src/deriving/debug.rs b/compiler/rustc_builtin_macros/src/deriving/debug.rs index 004b13bcc333d..ce842b93efdb2 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,165 @@ 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])) } + +/// 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, unsafe { +/// const __NAMES: &str = "ABBBCC"; +/// const __OFFSET: [usize; 4] =[0, 1, 4, 6]; +/// let __d = ::core::intrinsics::discriminant_value(self) as usize; +/// let __start = *__OFFSET.get_unchecked(__d); +/// let __end = *__OFFSET.get_unchecked(__d + 1); +/// __NAMES.get_unchecked(__start..__end) +/// }) +/// } +/// } +/// ``` +fn show_fieldless_enum_concat_str( + cx: &ExtCtxt<'_>, + span: Span, + def: &EnumDef, +) -> Option> { + let variant_count = def.variants.len(); + + 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); + + // __d & __d + 1 expressions + let discriminant_expr = cx.expr_ident(span, discriminant_ident); + 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, + 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); + + // 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, + 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); + + // __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( + Some(start_index_expr), + Some(end_index_expr), + rustc_ast::RangeLimits::HalfOpen, + ), + ); + + // __NAMES.get_unchecked(__start..__end] + let name_get_unchecked_call_expr = cx.expr_method_call( + span, + cx.expr_ident(span, names_ident), + Ident::from_str_and_span("get_unchecked", span), + thin_vec![slice_range_expr], + ); + + 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, + 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.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..c879ae72d72ab 100644 --- a/tests/ui/deriving/deriving-all-codegen.stdout +++ b/tests/ui/deriving/deriving-all-codegen.stdout @@ -1236,10 +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, - match self { - Fieldless::A => "A", - Fieldless::B => "B", - Fieldless::C => "C", + unsafe { + const __NAMES: &str = "ABC"; + const __OFFSET: [usize; 4] = [0usize, 1usize, 2usize, 3usize]; + let __d = + ::core::intrinsics::discriminant_value(self) as usize; + let __start = *__OFFSET.get_unchecked(__d); + let __end = *__OFFSET.get_unchecked(__d + 1usize); + __NAMES.get_unchecked(__start..__end) }) } } @@ -1294,6 +1298,25 @@ 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, + unsafe { + const __NAMES: &str = "ABBBCC"; + const __OFFSET: [usize; 4] = [0usize, 1usize, 4usize, 6usize]; + let __d = + ::core::intrinsics::discriminant_value(self) as usize; + let __start = *__OFFSET.get_unchecked(__d); + let __end = *__OFFSET.get_unchecked(__d + 1usize); + __NAMES.get_unchecked(__start..__end) + }) + } +} + // An enum with multiple fieldless and fielded variants. enum Mixed {