diff --git a/ILSpy.Tests/Metadata/FlagsTooltipTests.cs b/ILSpy.Tests/Metadata/FlagsTooltipTests.cs index 029cce143..5c5bcd0be 100644 --- a/ILSpy.Tests/Metadata/FlagsTooltipTests.cs +++ b/ILSpy.Tests/Metadata/FlagsTooltipTests.cs @@ -59,6 +59,29 @@ public class FlagsTooltipTests group.SelectedFlag.Name.Should().StartWith("Public"); } + [AvaloniaTest] + public void ForTypeAttributes_Includes_The_Type_Forwarder_Bit() + { + // 0x00200000 is the ECMA-335 Forwarder bit (II.23.1.15), set on type-forwarder + // ExportedType rows. System.Reflection.TypeAttributes has no member for it, so the + // enum-driven flag enumeration alone would leave it invisible in the tooltip. + var tooltip = FlagsTooltip.ForTypeAttributes((TypeAttributes)0x00200000); + + var flags = tooltip.Groups.OfType().Single().Flags; + var forwarder = flags.Single(f => f.Name.StartsWith("IsTypeForwarder")); + forwarder.IsSelected.Should().BeTrue("the Forwarder bit is set in the value"); + } + + [AvaloniaTest] + public void ForTypeAttributes_Leaves_The_Type_Forwarder_Bit_Unchecked_When_Clear() + { + var tooltip = FlagsTooltip.ForTypeAttributes(TypeAttributes.Public | TypeAttributes.Sealed); + + var flags = tooltip.Groups.OfType().Single().Flags; + var forwarder = flags.Single(f => f.Name.StartsWith("IsTypeForwarder")); + forwarder.IsSelected.Should().BeFalse("the Forwarder bit is not set in the value"); + } + [AvaloniaTest] public void MetadataCellTooltip_Renders_A_FlagsTooltip_As_A_Control() { diff --git a/ILSpy/Metadata/FlagsTooltip.cs b/ILSpy/Metadata/FlagsTooltip.cs index 597c873d9..079c34811 100644 --- a/ILSpy/Metadata/FlagsTooltip.cs +++ b/ILSpy/Metadata/FlagsTooltip.cs @@ -74,13 +74,22 @@ namespace ILSpy.Metadata public static FlagsTooltip ForTypeAttributes(TypeAttributes attributes) { const TypeAttributes otherFlagsMask = ~(TypeAttributes.VisibilityMask | TypeAttributes.LayoutMask | TypeAttributes.ClassSemanticsMask | TypeAttributes.StringFormatMask | TypeAttributes.CustomFormatMask); + // ECMA-335 II.23.1.15 Forwarder: set on ExportedType rows that forward the type to + // another assembly. System.Reflection.TypeAttributes has no member for the bit, so + // the enum-driven GetFlags enumeration cannot surface it; inject it explicitly. + const TypeAttributes isTypeForwarder = (TypeAttributes)0x00200000; return new FlagsTooltip { FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Visibility: ", (int)TypeAttributes.VisibilityMask, (int)(attributes & TypeAttributes.VisibilityMask), new Flag("NotPublic (0000)", 0, false), includeAny: false), FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Class layout: ", (int)TypeAttributes.LayoutMask, (int)(attributes & TypeAttributes.LayoutMask), new Flag("AutoLayout (0000)", 0, false), includeAny: false), FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Class semantics: ", (int)TypeAttributes.ClassSemanticsMask, (int)(attributes & TypeAttributes.ClassSemanticsMask), new Flag("Class (0000)", 0, false), includeAny: false), FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "String format: ", (int)TypeAttributes.StringFormatMask, (int)(attributes & TypeAttributes.StringFormatMask), new Flag("AnsiClass (0000)", 0, false), includeAny: false), FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Custom format: ", (int)TypeAttributes.CustomFormatMask, (int)(attributes & TypeAttributes.CustomFormatMask), new Flag("Value0 (0000)", 0, false), includeAny: false), - FlagGroup.CreateMultipleChoiceGroup(typeof(TypeAttributes), "Flags:", (int)otherFlagsMask, (int)(attributes & otherFlagsMask), includeAll: false), + new MultipleChoiceGroup( + FlagGroup.GetFlags(typeof(TypeAttributes), (int)otherFlagsMask, (int)(attributes & otherFlagsMask)) + .Append(new Flag($"IsTypeForwarder ({(int)isTypeForwarder:X4})", (int)isTypeForwarder, (attributes & isTypeForwarder) != 0))) { + Header = "Flags:", + SelectedFlags = (int)(attributes & otherFlagsMask), + }, }; } }