Skip to content

Beautify enum declarations by referencing previously declared members - #3902

Merged
siegfriedpammer merged 1 commit into
masterfrom
enum-member-references
Jul 22, 2026
Merged

Beautify enum declarations by referencing previously declared members#3902
siegfriedpammer merged 1 commit into
masterfrom
enum-member-references

Conversation

@siegfriedpammer

@siegfriedpammer siegfriedpammer commented Jul 21, 2026

Copy link
Copy Markdown
Member

Problem

Enum declarations are decompiled with every member shown as a bare number, even when the original source referenced other members: All = 7 instead of All = Item1 | Item2 | Item3, Item2B = 2 instead of Item2B = Item2A. (No open issue; this revives and completes the old stash/beautify-enum-member-declarations experiment.)

Solution

Enum member initializers that duplicate an earlier member now reference it, and combined [Flags] values are shown as a union of their earlier single-bit members or as a complement:

[Flags]
public enum SimpleFlagsEnum
{
	None = 0,
	Item1 = 1,
	Item2 = 2,
	Item3 = 4,
	All = Item1 | Item2 | Item3   // was: All = 7
}

public enum EnumDuplicateItemTest
{
	Item0 = 0,
	Item1 = 1,
	Item2A = 2,
	Item2B = Item2A               // was: Item2B = 2
}

Guardrails keep the output faithful to how such enums are written by hand:

  • Only previously declared members are referenced (field row order), so forward references never appear and the output always compiles.
  • A multi-bit value lying entirely within a larger, earlier-declared member is treated as a field encoding inside that mask rather than a flag union and stays numeric; zero-valued members of [Flags] enums stay numeric too (mask-style enums routinely have several unrelated zero members, e.g. MethodAttributes.PrivateScope/ReuseSlot).
  • The ~X complement form is suppressed in byte/ushort enum declarations, where the initializer constant folds in int and would not compile (CS0031); expression contexts keep it for every underlying type.
  • Enums with unusual underlying types (bool, native int) keep the plain constant conversion.

With these rules, decompiling System.Private.CoreLib reproduces the hand-written declarations of TypeAttributes, MethodAttributes, FileAttributes and AttributeTargets (including All = Assembly | Module | ... | GenericParameter) almost verbatim: numeric exactly where the original sources are numeric, while unambiguous aliases such as NewSlot = VtableLayoutMask are referenced.

Most in need of attention: the two output-policy heuristics (mask-encoding suppression and numeric zeros in [Flags] declarations) are aesthetic trade-offs; the fixtures pin the chosen behavior.

  • At least one test covering the code changed — new Pretty fixtures in EnumTests cover mask-family enums, unsigned/byte/short flags, complements at declaration and usage sites, and zero/non-flags duplicates; full suite green (all non-Correctness tests 2171 passed, Correctness 197 passed).

The code and this description were largely written by an AI agent (Claude Code), reviewed by me.

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR improves enum decompilation output by emitting member references (and [Flags] unions/complements) instead of always printing raw numeric literals, while enforcing ordering/compilability rules for enum member initializers.

Changes:

  • Extend TypeSystemAstBuilder.ConvertEnumValue to support enum-member-initializer context (unqualified references, avoid self/forward references, suppress non-compiling complements).
  • Update enum field decompilation to use the enhanced enum-value conversion for standard integral underlying types.
  • Add/extend pretty-test fixtures to pin the new enum formatting behavior across flags, duplicates, masks, complements, and underlying types.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs Implements the enum “beautification” logic (member references, flags unions, initializer-specific guardrails).
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs Switches enum member initializer generation to use ConvertEnumValue when safe, falling back for unusual underlying types.
ICSharpCode.Decompiler.Tests/TestCases/Pretty/EnumTests.cs Adds fixtures covering duplicates, flags unions, complement handling, mask-family heuristics, and underlying-type edge cases.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs Outdated
Comment thread ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs Outdated
@siegfriedpammer
siegfriedpammer force-pushed the enum-member-references branch from e2d8f6c to 1c21c83 Compare July 22, 2026 05:28
Enum members whose value duplicates an earlier member now reference it
(Item2B = Item2A), [Flags] members combine earlier single-bit members
(All = Item1 | Item2 | Item3) or their complement (NotItem1 = ~Item1)
instead of showing a bare number.

Several guardrails keep the output faithful to how such enums are
written by hand: only previously declared members are referenced (field
row order); a multi-bit value lying entirely within a larger, earlier
member is a field encoding inside that mask, not a flag union, and
stays numeric, as do zero-valued members of [Flags] enums, which
routinely have several unrelated zero members. The ~X form is
suppressed in byte/ushort enum declarations, where the initializer
constant folds in int and would not compile. Enums with unusual
underlying types (bool, native int) keep the plain constant conversion.
With these rules, decompiling System.Private.CoreLib reproduces the
hand-written declarations of TypeAttributes, MethodAttributes,
AttributeTargets and FileAttributes almost verbatim.

Assisted-by: Claude:claude-fable-5:Claude Code
@siegfriedpammer
siegfriedpammer force-pushed the enum-member-references branch from 1c21c83 to 3b53feb Compare July 22, 2026 05:33
@siegfriedpammer
siegfriedpammer merged commit fddd622 into master Jul 22, 2026
13 checks passed
@siegfriedpammer
siegfriedpammer deleted the enum-member-references branch July 22, 2026 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants