rust-lang/rust · #162535
Reland debug impl for fieldless enums with concatenated string without using discriminant_value
compiler/rustc_builtin_macros/src/deriving/debug.rs94 + / 0 −
@@ -232,6 +232,9 @@ fn show_fieldless_enum( def: &EnumDef, type_ident: Ident, ) -> BlockOrExpr {+ if let Some(expr) = show_fieldless_enum_concat_str(cx, span, def, type_ident) {+ return BlockOrExpr::new_expr(expr);+ } let fmt = cx.expr_name(span, sym::f); let arms = def .variants@@ -256,3 +259,94 @@ fn show_fieldless_enum( let fn_path_write_str = cx.std_path(span, &[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 large enums with no fields. Builds:+/// ```text+/// impl ::core::fmt::Debug for A {+/// fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {+/// ::core::fmt::Formatter::debug_c_like_enum_write_str(f,+/// "A B C D E F",+/// match self {+/// A::A => 0usize,+/// A::B => 2usize,+/// A::C => 4usize,+/// A::D => 6usize,+/// A::E => 8usize,+/// A::F => 10usize,+/// })+/// }+/// }+/// ```+fn show_fieldless_enum_concat_str(+ cx: &ExtCtxt<'_>,+ span: Span,+ def: &EnumDef,+ type_ident: Ident,+) -> Option<Box<ast::Expr>> {+ // Minimum variant count where this optimization starts to pay off.+ // Initial value of 10 came from https://github.com/rust-lang/rust/pull/155452.+ // Refined with the new impl in https://github.com/rust-lang/rust/pull/162535.+ // Tested with thresholds 10,5,2 on rustc-perf+ // and by comparing MIR sizes for various enums in GodBolt.+ //+ // `match` with strings generates:+ // - a local for each arm+ // - a basic block with 5 statements for each arm,+ //+ // `match` with integers to index a concatenated string generates+ // - a single local for the string+ // - a single local for the index+ // - a basic block with 2 statements for each arm+ //+ // This reduces borrowck costs on larger enums.+ const THRESHOLD: usize = 5;+ let variants_count = def.variants.len();+ if variants_count < THRESHOLD {+ return None;+ }+ let fmt = cx.expr_name(span, sym::f);++ let mut concatenated_names = String::new();++ let arms = def+ .variants+ .iter()+ .enumerate()+ .map(|(i, v)| {+ let name_offset = concatenated_names.len();++ let name = v.ident.name.as_str();+ concatenated_names.push_str(name);++ let is_last = i == def.variants.len() - 1;+ if !is_last {+ concatenated_names.push(' ');+ }++ let variant_path = cx.path(span, &[type_ident, v.ident]);+ let pat = match &v.data {+ ast::VariantData::Tuple(fields, _) => {+ debug_assert!(fields.is_empty());+ cx.pat_tuple_struct(span, variant_path, ThinVec::new())+ }+ ast::VariantData::Struct { fields, .. } => {+ debug_assert!(fields.is_empty());+ cx.pat_struct(span, variant_path, ThinVec::new())+ }+ ast::VariantData::Unit(_) => cx.pat_path(span, variant_path),+ };++ cx.arm(span, pat, cx.expr_usize(span, name_offset))+ })+ .collect::<ThinVec<_>>();++ let names_str_body = cx.expr_str(span, Symbol::intern(&concatenated_names));++ let variant_index_expr = cx.expr_match(span, cx.expr_self(span), arms);++ let fn_path = cx.std_path(span, &[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_str_body, variant_index_expr]);++ Some(call_expr)+}compiler/rustc_span/src/symbol.rs1 + / 0 −
@@ -804,6 +804,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,library/core/src/fmt/mod.rs13 + / 0 −
@@ -2619,6 +2619,19 @@ 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.+ /// Names should be separated by space.+ #[doc(hidden)]+ #[unstable(feature = "fmt_helpers_for_derive", issue = "none")]+ pub fn debug_c_like_enum_write_str<'b>(+ &'b mut self,+ names: &str,+ start_index: usize,+ ) -> Result {+ self.write_str(names[start_index..].split(' ').next().unwrap())+ }+ /// Creates a `DebugTuple` builder designed to assist with creation of /// `fmt::Debug` implementations for tuple structs. ///tests/ui/derives/debug/derive-Debug-enum-variants.rs20 + / 0 −
@@ -13,6 +13,17 @@ enum Bar { D, } +#[derive(Debug)]+enum Large {+ A,+ Bar,+ C,+ Distance,+ Emily,+ F,+ G,+}+ pub fn main() { // Test variant with data let foo_a = Foo::A(22);@@ -27,4 +38,13 @@ pub fn main() { // Test unit variant from different enum assert_eq!("D".to_string(), format!("{:?}", Bar::D));++ // Test large c-like enum, those use a different implementation.+ assert_eq!("A".to_string(), format!("{:?}", Large::A));+ assert_eq!("Bar".to_string(), format!("{:?}", Large::Bar));+ assert_eq!("C".to_string(), format!("{:?}", Large::C));+ assert_eq!("Distance".to_string(), format!("{:?}", Large::Distance));+ assert_eq!("Emily".to_string(), format!("{:?}", Large::Emily));+ assert_eq!("F".to_string(), format!("{:?}", Large::F));+ assert_eq!("G".to_string(), format!("{:?}", Large::G)); }tests/ui/derives/deriving-all-codegen.rs23 + / 0 −
@@ -154,6 +154,29 @@ 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 10 variants.+#[derive(Debug)]+enum Fieldless10 {+ AAAAA,+ BBBB,+ CC,+ DDDDDDDD,+ E,+ FFFFFFFFFFFFF,+ GGGGGG,+ Hatsune,+ IIIIIII,+ JJJJJJJJJ,+}+ // An enum with multiple fieldless and fielded variants. #[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] enum Mixed {tests/ui/derives/deriving-all-codegen.stdout49 + / 0 −
@@ -1219,6 +1219,55 @@ 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 10 variants.+enum Fieldless10 {+ AAAAA,+ BBBB,+ CC,+ DDDDDDDD,+ E,+ FFFFFFFFFFFFF,+ GGGGGG,+ Hatsune,+ IIIIIII,+ JJJJJJJJJ,+}+#[automatically_derived]+impl ::core::fmt::Debug for Fieldless10 {+ #[inline]+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {+ ::core::fmt::Formatter::debug_c_like_enum_write_str(f,+ "AAAAA BBBB CC DDDDDDDD E FFFFFFFFFFFFF GGGGGG Hatsune IIIIIII JJJJJJJJJ",+ match self {+ Fieldless10::AAAAA => 0usize,+ Fieldless10::BBBB => 6usize,+ Fieldless10::CC => 11usize,+ Fieldless10::DDDDDDDD => 14usize,+ Fieldless10::E => 23usize,+ Fieldless10::FFFFFFFFFFFFF => 25usize,+ Fieldless10::GGGGGG => 39usize,+ Fieldless10::Hatsune => 46usize,+ Fieldless10::IIIIIII => 54usize,+ Fieldless10::JJJJJJJJJ => 62usize,+ })+ }+}+ // An enum with multiple fieldless and fielded variants. enum Mixed {