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
179 changes: 172 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,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()
Expand All @@ -250,6 +256,165 @@ 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 {
/// ::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<Box<ast::Expr>> {
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::<Option<ThinVec<_>>>()?;
Comment on lines +284 to +288
Copy link
Copy Markdown
Member Author

@makai410 makai410 Apr 18, 2026

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 & __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,
})))
}
8 changes: 8 additions & 0 deletions tests/ui/deriving/deriving-all-codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
31 changes: 27 additions & 4 deletions tests/ui/deriving/deriving-all-codegen.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}
}
Expand Down Expand Up @@ -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 {

Expand Down
Loading