Browse Source

Restore per-cell tooltips across the metadata tables

The metadata-grid hover infrastructure shipped but no table entry fed it,
so every cell tooltip the previous version had was silently gone: the
heap-offset/value hints on string and blob columns, the entity description
on token columns, and the per-bit breakdown on flag columns.

Reinstate all of them. FlagsTooltip renders the rich per-bit view (a
checkbox per flag plus the selected member of each mutually exclusive
sub-range) instead of a flat string; GenerateTooltip on the table base
describes what a token column points at; the remaining columns expose
their heap-offset and value hints. MetadataCellTooltip now hands a
FlagsTooltip back as a built control and stringifies everything else.

Assisted-by: Claude:claude-opus-4-8:Claude Code
pull/3755/head
Siegfried Pammer 1 month ago
parent
commit
270ef0f519
  1. 81
      ILSpy.Tests/Metadata/FlagsTooltipTests.cs
  2. 70
      ILSpy.Tests/Metadata/MetadataTooltipParityTests.cs
  3. 17
      ILSpy/Metadata/CorTables/AssemblyRefTableTreeNode.cs
  4. 12
      ILSpy/Metadata/CorTables/AssemblyTableTreeNode.cs
  5. 9
      ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs
  6. 11
      ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs
  7. 12
      ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs
  8. 15
      ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs
  9. 12
      ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs
  10. 9
      ILSpy/Metadata/CorTables/EventTableTreeNode.cs
  11. 18
      ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs
  12. 9
      ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs
  13. 9
      ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs
  14. 9
      ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs
  15. 12
      ILSpy/Metadata/CorTables/FieldTableTreeNode.cs
  16. 13
      ILSpy/Metadata/CorTables/FileTableTreeNode.cs
  17. 20
      ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs
  18. 10
      ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs
  19. 28
      ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs
  20. 12
      ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs
  21. 7
      ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs
  22. 8
      ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs
  23. 9
      ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs
  24. 14
      ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs
  25. 22
      ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs
  26. 29
      ILSpy/Metadata/CorTables/MethodTableTreeNode.cs
  27. 2
      ILSpy/Metadata/CorTables/ModuleRefTableTreeNode.cs
  28. 8
      ILSpy/Metadata/CorTables/ModuleTableTreeNode.cs
  29. 12
      ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs
  30. 6
      ILSpy/Metadata/CorTables/ParamTableTreeNode.cs
  31. 12
      ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs
  32. 9
      ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs
  33. 9
      ILSpy/Metadata/CorTables/PtrTableTreeNode.cs
  34. 3
      ILSpy/Metadata/CorTables/StandAloneSigTableTreeNode.cs
  35. 60
      ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs
  36. 7
      ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs
  37. 10
      ILSpy/Metadata/CorTables/TypeSpecTableTreeNode.cs
  38. 11
      ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs
  39. 41
      ILSpy/Metadata/DebugTables/DocumentTableTreeNode.cs
  40. 2
      ILSpy/Metadata/DebugTables/LocalConstantTableTreeNode.cs
  41. 3
      ILSpy/Metadata/DebugTables/LocalScopeTableTreeNode.cs
  42. 6
      ILSpy/Metadata/DebugTables/LocalVariableTableTreeNode.cs
  43. 46
      ILSpy/Metadata/DebugTables/MethodDebugInformationTableTreeNode.cs
  44. 23
      ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs
  45. 184
      ILSpy/Metadata/FlagsTooltip.cs
  46. 14
      ILSpy/Metadata/MetadataCellTooltip.cs
  47. 74
      ILSpy/Metadata/MetadataTableTreeNode.cs

81
ILSpy.Tests/Metadata/FlagsTooltipTests.cs

@ -0,0 +1,81 @@ @@ -0,0 +1,81 @@
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Headless.NUnit;
using AwesomeAssertions;
using ILSpy.Metadata;
using NUnit.Framework;
namespace ICSharpCode.ILSpy.Tests.Metadata;
/// <summary>
/// The rich flags tooltip breaks an enum value into per-bit groups: multiple-choice groups render a
/// checkbox per flag with the set bits checked, single-choice groups render the selected member of a
/// mutually exclusive sub-range. (Regression: the Avalonia port had only a plain-text fallback.)
/// </summary>
[TestFixture]
public class FlagsTooltipTests
{
[AvaloniaTest]
public void MultipleChoiceGroup_Checks_The_Set_Bits()
{
var group = FlagGroup.CreateMultipleChoiceGroup(typeof(MethodAttributes), "Flags:",
(int)MethodAttributes.Static, (int)MethodAttributes.Static, includeAll: false);
var view = (StackPanel)group.Build();
var statik = view.Children.OfType<CheckBox>()
.Single(c => ((string?)c.Content)!.StartsWith("Static"));
statik.IsChecked.Should().BeTrue("the Static bit is set in the selected value");
}
[AvaloniaTest]
public void SingleChoiceGroup_Shows_The_Selected_Member()
{
var group = FlagGroup.CreateSingleChoiceGroup(typeof(MethodAttributes), "Accessibility: ",
(int)MethodAttributes.MemberAccessMask, (int)MethodAttributes.Public, includeAny: false);
group.SelectedFlag.Name.Should().StartWith("Public");
}
[AvaloniaTest]
public void MetadataCellTooltip_Renders_A_FlagsTooltip_As_A_Control()
{
var entry = new EntryWithFlags();
var resolved = MetadataCellTooltip.Resolve(entry, "Attributes");
resolved.Should().BeAssignableTo<Control>("a FlagsTooltip value renders as the rich control");
}
sealed class EntryWithFlags
{
public MethodAttributes Attributes => MethodAttributes.Public | MethodAttributes.Static;
public object AttributesTooltip => new FlagsTooltip {
FlagGroup.CreateSingleChoiceGroup(typeof(MethodAttributes), "Accessibility: ",
(int)MethodAttributes.MemberAccessMask, (int)(Attributes & MethodAttributes.MemberAccessMask), includeAny: false),
FlagGroup.CreateMultipleChoiceGroup(typeof(MethodAttributes), "Flags:",
~(int)MethodAttributes.MemberAccessMask, (int)Attributes, includeAll: false),
};
}
}

70
ILSpy.Tests/Metadata/MetadataTooltipParityTests.cs

@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Headless.NUnit;
using AwesomeAssertions;
using ILSpy.Metadata;
using ILSpy.Metadata.CorTables;
using ILSpy.TreeNodes;
using NUnit.Framework;
namespace ICSharpCode.ILSpy.Tests.Metadata;
/// <summary>
/// End-to-end check that the per-cell metadata tooltips restored across the table entries actually
/// resolve against a real assembly: a heap-offset string tooltip, a token tooltip routed through
/// <c>GenerateTooltip</c>, and a rich per-bit <see cref="FlagsTooltip"/> rendered as a control.
/// </summary>
[TestFixture]
public class MetadataTooltipParityTests
{
[AvaloniaTest]
public async Task TypeDef_Row_Tooltips_Resolve_For_System_Object()
{
var (_, vm) = await TestHarness.BootAsync();
var typeDefNode = vm.AssemblyTreeModel.FindCoreLib()
.GetChild<MetadataTreeNode>()
.GetChild<MetadataTablesTreeNode>()
.GetChild<TypeDefTableTreeNode>();
vm.AssemblyTreeModel.SelectNode(typeDefNode);
var tab = await vm.DockWorkspace.WaitForMetadataTabAsync();
var objectRow = tab.Items.Cast<TypeDefTableTreeNode.TypeDefEntry>()
.Single(e => e.Name == "Object" && e.Namespace == "System");
// Heap-offset string tooltip on the Name column.
MetadataCellTooltip.Resolve(objectRow, "Name").Should().BeOfType<string>()
.Which.Should().Contain("Object");
// Token tooltip routed through GenerateTooltip (System.Object's first method).
MetadataCellTooltip.Resolve(objectRow, "MethodList").Should().BeOfType<string>()
.Which.Should().NotBeNullOrWhiteSpace();
// Rich per-bit flags breakdown renders as a control, not a plain string.
MetadataCellTooltip.Resolve(objectRow, "Attributes").Should().BeAssignableTo<Control>();
}
}

17
ILSpy/Metadata/CorTables/AssemblyRefTableTreeNode.cs

@ -67,11 +67,28 @@ namespace ILSpy.Metadata.CorTables @@ -67,11 +67,28 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public AssemblyFlags Flags => assemblyRef.Flags;
public object FlagsTooltip => new FlagsTooltip((int)assemblyRef.Flags, null) {
FlagGroup.CreateMultipleChoiceGroup(typeof(AssemblyFlags), selectedValue: (int)assemblyRef.Flags, includeAll: false)
};
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int PublicKeyOrToken => MetadataTokens.GetHeapOffset(assemblyRef.PublicKeyOrToken);
public string? PublicKeyOrTokenTooltip {
get {
if (assemblyRef.PublicKeyOrToken.IsNil)
return null;
System.Collections.Immutable.ImmutableArray<byte> token = metadataFile.Metadata.GetBlobContent(assemblyRef.PublicKeyOrToken);
return token.ToHexString(token.Length);
}
}
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(assemblyRef.Name):X} \"{Name}\"";
public string Name => metadataFile.Metadata.GetString(assemblyRef.Name);
public string CultureTooltip => $"{MetadataTokens.GetHeapOffset(assemblyRef.Culture):X} \"{Culture}\"";
public string Culture => metadataFile.Metadata.GetString(assemblyRef.Culture);
public AssemblyRefEntry(MetadataFile metadataFile, AssemblyReferenceHandle handle)

12
ILSpy/Metadata/CorTables/AssemblyTableTreeNode.cs

@ -62,13 +62,25 @@ namespace ILSpy.Metadata.CorTables @@ -62,13 +62,25 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X4")]
public AssemblyHashAlgorithm HashAlgorithm => assembly.HashAlgorithm;
public object HashAlgorithmTooltip => new FlagsTooltip() {
FlagGroup.CreateSingleChoiceGroup(typeof(AssemblyHashAlgorithm), selectedValue: (int)assembly.HashAlgorithm, defaultFlag: new Flag("None (0000)", 0, false), includeAny: false)
};
[ColumnInfo("X4")]
public AssemblyFlags Flags => assembly.Flags;
public object FlagsTooltip => new FlagsTooltip() {
FlagGroup.CreateMultipleChoiceGroup(typeof(AssemblyFlags), selectedValue: (int)assembly.Flags, includeAll: false)
};
public Version Version => assembly.Version;
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(assembly.Name):X} \"{Name}\"";
public string Name => metadata.GetString(assembly.Name);
public string CultureTooltip => $"{MetadataTokens.GetHeapOffset(assembly.Culture):X} \"{Culture}\"";
public string Culture => metadata.GetString(assembly.Culture);
public AssemblyEntry(MetadataReader metadata, int metadataOffset)

9
ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs

@ -50,7 +50,7 @@ namespace ILSpy.Metadata.CorTables @@ -50,7 +50,7 @@ namespace ILSpy.Metadata.CorTables
ushort packingSize = reader.ReadUInt16();
uint classSize = reader.ReadUInt32();
int parentRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
list.Add(new ClassLayoutEntry(rid, packingSize, classSize, MetadataTokens.TypeDefinitionHandle(parentRow)));
list.Add(new ClassLayoutEntry(metadataFile, rid, packingSize, classSize, MetadataTokens.TypeDefinitionHandle(parentRow)));
}
return list;
}
@ -65,14 +65,19 @@ namespace ILSpy.Metadata.CorTables @@ -65,14 +65,19 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Parent => MetadataTokens.GetToken(parent);
string? parentTooltip;
public string? ParentTooltip => GenerateTooltip(ref parentTooltip, metadataFile, parent);
public ushort PackingSize { get; }
public uint ClassSize { get; }
readonly MetadataFile metadataFile;
readonly TypeDefinitionHandle parent;
public ClassLayoutEntry(int rid, ushort packingSize, uint classSize, TypeDefinitionHandle parent)
public ClassLayoutEntry(MetadataFile metadataFile, int rid, ushort packingSize, uint classSize, TypeDefinitionHandle parent)
{
this.metadataFile = metadataFile;
RID = rid;
PackingSize = packingSize;
ClassSize = classSize;

11
ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs

@ -63,12 +63,23 @@ namespace ILSpy.Metadata.CorTables @@ -63,12 +63,23 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public ConstantTypeCode Type => constant.TypeCode;
public string TypeTooltip => constant.TypeCode.ToString();
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Parent => MetadataTokens.GetToken(constant.Parent);
string? parentTooltip;
public string? ParentTooltip => GenerateTooltip(ref parentTooltip, metadataFile, constant.Parent);
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Value => MetadataTokens.GetHeapOffset(constant.Value);
public string? ValueTooltip {
get {
return null;
}
}
public ConstantEntry(MetadataFile metadataFile, ConstantHandle handle)
{
this.metadataFile = metadataFile;

12
ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs

@ -64,12 +64,24 @@ namespace ILSpy.Metadata.CorTables @@ -64,12 +64,24 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Parent => MetadataTokens.GetToken(customAttr.Parent);
string? parentTooltip;
public string? ParentTooltip => GenerateTooltip(ref parentTooltip, metadataFile, customAttr.Parent);
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Constructor => MetadataTokens.GetToken(customAttr.Constructor);
string? constructorTooltip;
public string? ConstructorTooltip => GenerateTooltip(ref constructorTooltip, metadataFile, customAttr.Constructor);
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Value => MetadataTokens.GetHeapOffset(customAttr.Value);
public string? ValueTooltip {
get {
return null;
}
}
public CustomAttributeEntry(MetadataFile metadataFile, CustomAttributeHandle handle)
{
this.metadataFile = metadataFile;

15
ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs

@ -64,12 +64,27 @@ namespace ILSpy.Metadata.CorTables @@ -64,12 +64,27 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Parent => MetadataTokens.GetToken(declSecAttr.Parent);
string? parentTooltip;
public string? ParentTooltip => GenerateTooltip(ref parentTooltip, metadataFile, declSecAttr.Parent);
[ColumnInfo("X8")]
public DeclarativeSecurityAction Action => declSecAttr.Action;
public string ActionTooltip {
get {
return declSecAttr.Action.ToString();
}
}
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int PermissionSet => MetadataTokens.GetHeapOffset(declSecAttr.PermissionSet);
public string? PermissionSetTooltip {
get {
return null;
}
}
public DeclSecurityEntry(MetadataFile metadataFile, DeclarativeSecurityAttributeHandle handle)
{
this.metadataFile = metadataFile;

12
ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs

@ -48,7 +48,7 @@ namespace ILSpy.Metadata.CorTables @@ -48,7 +48,7 @@ namespace ILSpy.Metadata.CorTables
{
int parentRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
int eventListRow = eventDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
list.Add(new EventMapEntry(rid, MetadataTokens.TypeDefinitionHandle(parentRow), MetadataTokens.EventDefinitionHandle(eventListRow)));
list.Add(new EventMapEntry(metadataFile, rid, MetadataTokens.TypeDefinitionHandle(parentRow), MetadataTokens.EventDefinitionHandle(eventListRow)));
}
return list;
}
@ -63,14 +63,22 @@ namespace ILSpy.Metadata.CorTables @@ -63,14 +63,22 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Parent => MetadataTokens.GetToken(parent);
string? parentTooltip;
public string? ParentTooltip => GenerateTooltip(ref parentTooltip, metadataFile, parent);
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int EventList => MetadataTokens.GetToken(eventList);
string? eventListTooltip;
public string? EventListTooltip => GenerateTooltip(ref eventListTooltip, metadataFile, eventList);
readonly MetadataFile metadataFile;
readonly TypeDefinitionHandle parent;
readonly EventDefinitionHandle eventList;
public EventMapEntry(int rid, TypeDefinitionHandle parent, EventDefinitionHandle eventList)
public EventMapEntry(MetadataFile metadataFile, int rid, TypeDefinitionHandle parent, EventDefinitionHandle eventList)
{
this.metadataFile = metadataFile;
RID = rid;
this.parent = parent;
this.eventList = eventList;

9
ILSpy/Metadata/CorTables/EventTableTreeNode.cs

@ -64,11 +64,20 @@ namespace ILSpy.Metadata.CorTables @@ -64,11 +64,20 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public EventAttributes Attributes => eventDef.Attributes;
public object AttributesTooltip => new FlagsTooltip {
FlagGroup.CreateMultipleChoiceGroup(typeof(EventAttributes), selectedValue: (int)eventDef.Attributes, includeAll: false),
};
public string Name => metadataFile.Metadata.GetString(eventDef.Name);
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(eventDef.Name):X} \"{Name}\"";
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Type => MetadataTokens.GetToken(eventDef.Type);
string? typeTooltip;
public string? TypeTooltip => GenerateTooltip(ref typeTooltip, metadataFile, eventDef.Type);
public EventDefEntry(MetadataFile metadataFile, EventDefinitionHandle handle)
{
this.metadataFile = metadataFile;

18
ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs

@ -65,16 +65,34 @@ namespace ILSpy.Metadata.CorTables @@ -65,16 +65,34 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public TypeAttributes Attributes => type.Attributes;
const TypeAttributes otherFlagsMask = ~(TypeAttributes.VisibilityMask | TypeAttributes.LayoutMask | TypeAttributes.ClassSemanticsMask | TypeAttributes.StringFormatMask | TypeAttributes.CustomFormatMask);
public object AttributesTooltip => new FlagsTooltip {
FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Visibility: ", (int)TypeAttributes.VisibilityMask, (int)(type.Attributes & TypeAttributes.VisibilityMask), new Flag("NotPublic (0000)", 0, false), includeAny: false),
FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Class layout: ", (int)TypeAttributes.LayoutMask, (int)(type.Attributes & TypeAttributes.LayoutMask), new Flag("AutoLayout (0000)", 0, false), includeAny: false),
FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Class semantics: ", (int)TypeAttributes.ClassSemanticsMask, (int)(type.Attributes & TypeAttributes.ClassSemanticsMask), new Flag("Class (0000)", 0, false), includeAny: false),
FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "String format: ", (int)TypeAttributes.StringFormatMask, (int)(type.Attributes & TypeAttributes.StringFormatMask), new Flag("AnsiClass (0000)", 0, false), includeAny: false),
FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Custom format: ", (int)TypeAttributes.CustomFormatMask, (int)(type.Attributes & TypeAttributes.CustomFormatMask), new Flag("Value0 (0000)", 0, false), includeAny: false),
FlagGroup.CreateMultipleChoiceGroup(typeof(TypeAttributes), "Flags:", (int)otherFlagsMask, (int)(type.Attributes & otherFlagsMask), includeAll: false),
};
[ColumnInfo("X8")]
public int TypeDefId => type.GetTypeDefinitionId();
public string TypeNameTooltip => $"{MetadataTokens.GetHeapOffset(type.Name):X} \"{TypeName}\"";
public string TypeName => metadataFile.Metadata.GetString(type.Name);
public string TypeNamespaceTooltip => $"{MetadataTokens.GetHeapOffset(type.Namespace):X} \"{TypeNamespace}\"";
public string TypeNamespace => metadataFile.Metadata.GetString(type.Namespace);
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Implementation => MetadataTokens.GetToken(type.Implementation);
string? implementationTooltip;
public string? ImplementationTooltip => GenerateTooltip(ref implementationTooltip, metadataFile, type.Implementation);
public ExportedTypeEntry(MetadataFile metadataFile, ExportedTypeHandle handle, ExportedType type)
{
this.metadataFile = metadataFile;

9
ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs

@ -47,7 +47,7 @@ namespace ILSpy.Metadata.CorTables @@ -47,7 +47,7 @@ namespace ILSpy.Metadata.CorTables
{
int offset = reader.ReadInt32();
int fieldRow = fieldDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
list.Add(new FieldLayoutEntry(rid, offset, MetadataTokens.FieldDefinitionHandle(fieldRow)));
list.Add(new FieldLayoutEntry(metadataFile, rid, offset, MetadataTokens.FieldDefinitionHandle(fieldRow)));
}
return list;
}
@ -57,6 +57,7 @@ namespace ILSpy.Metadata.CorTables @@ -57,6 +57,7 @@ namespace ILSpy.Metadata.CorTables
// Use a verbatim fieldHandle local because C# 14 made `field` contextual inside property
// accessors — referencing it without `@` would name the auto-property's hidden
// backing field instead of this member.
readonly MetadataFile metadataFile;
readonly FieldDefinitionHandle fieldHandle;
public int RID { get; }
@ -67,11 +68,15 @@ namespace ILSpy.Metadata.CorTables @@ -67,11 +68,15 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Field => MetadataTokens.GetToken(fieldHandle);
string? fieldTooltip;
public string? FieldTooltip => GenerateTooltip(ref fieldTooltip, metadataFile, fieldHandle);
[ColumnInfo("X8")]
public int FieldOffset { get; }
public FieldLayoutEntry(int rid, int fieldOffset, FieldDefinitionHandle field)
public FieldLayoutEntry(MetadataFile metadataFile, int rid, int fieldOffset, FieldDefinitionHandle field)
{
this.metadataFile = metadataFile;
RID = rid;
FieldOffset = fieldOffset;
fieldHandle = field;

9
ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs

@ -49,7 +49,7 @@ namespace ILSpy.Metadata.CorTables @@ -49,7 +49,7 @@ namespace ILSpy.Metadata.CorTables
{
uint parentTag = (uint)(hasFieldMarshalRefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32());
int blobOffset = blobHeapSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
list.Add(new FieldMarshalEntry(rid,
list.Add(new FieldMarshalEntry(metadataFile, rid,
MetadataReaderHelpers.FromHasFieldMarshalTag(parentTag),
MetadataTokens.BlobHandle(blobOffset)));
}
@ -66,14 +66,19 @@ namespace ILSpy.Metadata.CorTables @@ -66,14 +66,19 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Parent => MetadataTokens.GetToken(parent);
string? parentTooltip;
public string? ParentTooltip => GenerateTooltip(ref parentTooltip, metadataFile, parent);
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int NativeType => MetadataTokens.GetHeapOffset(nativeType);
readonly MetadataFile metadataFile;
readonly EntityHandle parent;
readonly BlobHandle nativeType;
public FieldMarshalEntry(int rid, EntityHandle parent, BlobHandle nativeType)
public FieldMarshalEntry(MetadataFile metadataFile, int rid, EntityHandle parent, BlobHandle nativeType)
{
this.metadataFile = metadataFile;
RID = rid;
this.parent = parent;
this.nativeType = nativeType;

9
ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs

@ -48,7 +48,7 @@ namespace ILSpy.Metadata.CorTables @@ -48,7 +48,7 @@ namespace ILSpy.Metadata.CorTables
{
int rva = reader.ReadInt32();
int fieldRow = fieldDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
list.Add(new FieldRVAEntry(rid, rva, MetadataTokens.FieldDefinitionHandle(fieldRow)));
list.Add(new FieldRVAEntry(metadataFile, rid, rva, MetadataTokens.FieldDefinitionHandle(fieldRow)));
}
return list;
}
@ -56,6 +56,7 @@ namespace ILSpy.Metadata.CorTables @@ -56,6 +56,7 @@ namespace ILSpy.Metadata.CorTables
public sealed class FieldRVAEntry
{
// Verbatim fieldHandle — see FieldLayoutTableTreeNode for the C# 14 keyword note.
readonly MetadataFile metadataFile;
readonly FieldDefinitionHandle fieldHandle;
public int RID { get; }
@ -66,11 +67,15 @@ namespace ILSpy.Metadata.CorTables @@ -66,11 +67,15 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Field => MetadataTokens.GetToken(fieldHandle);
string? fieldTooltip;
public string? FieldTooltip => GenerateTooltip(ref fieldTooltip, metadataFile, fieldHandle);
[ColumnInfo("X8")]
public int RVA { get; }
public FieldRVAEntry(int rid, int rva, FieldDefinitionHandle field)
public FieldRVAEntry(MetadataFile metadataFile, int rid, int rva, FieldDefinitionHandle field)
{
this.metadataFile = metadataFile;
RID = rid;
RVA = rva;
fieldHandle = field;

12
ILSpy/Metadata/CorTables/FieldTableTreeNode.cs

@ -64,11 +64,23 @@ namespace ILSpy.Metadata.CorTables @@ -64,11 +64,23 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public FieldAttributes Attributes => fieldDef.Attributes;
const FieldAttributes otherFlagsMask = ~(FieldAttributes.FieldAccessMask);
public object AttributesTooltip => new FlagsTooltip() {
FlagGroup.CreateSingleChoiceGroup(typeof(FieldAttributes), "Field access: ", (int)FieldAttributes.FieldAccessMask, (int)(fieldDef.Attributes & FieldAttributes.FieldAccessMask), new Flag("CompilerControlled (0000)", 0, false), includeAny: false),
FlagGroup.CreateMultipleChoiceGroup(typeof(FieldAttributes), "Flags:", (int)otherFlagsMask, (int)(fieldDef.Attributes & otherFlagsMask), includeAll: false),
};
public string Name => metadataFile.Metadata.GetString(fieldDef.Name);
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(fieldDef.Name):X} \"{Name}\"";
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(fieldDef.Signature);
string? signatureTooltip;
public string? SignatureTooltip => GenerateTooltip(ref signatureTooltip, metadataFile, handle);
public FieldDefEntry(MetadataFile metadataFile, FieldDefinitionHandle handle)
{
this.metadataFile = metadataFile;

13
ILSpy/Metadata/CorTables/FileTableTreeNode.cs

@ -62,11 +62,24 @@ namespace ILSpy.Metadata.CorTables @@ -62,11 +62,24 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public int Attributes => assemblyFile.ContainsMetadata ? 1 : 0;
public string AttributesTooltip => assemblyFile.ContainsMetadata ? "ContainsMetaData" : "ContainsNoMetaData";
public string Name => metadataFile.Metadata.GetString(assemblyFile.Name);
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(assemblyFile.Name):X} \"{Name}\"";
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int HashValue => MetadataTokens.GetHeapOffset(assemblyFile.HashValue);
public string? HashValueTooltip {
get {
if (assemblyFile.HashValue.IsNil)
return null;
System.Collections.Immutable.ImmutableArray<byte> token = metadataFile.Metadata.GetBlobContent(assemblyFile.HashValue);
return token.ToHexString(token.Length);
}
}
public FileEntry(MetadataFile metadataFile, AssemblyFileHandle handle)
{
this.metadataFile = metadataFile;

20
ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs

@ -20,6 +20,8 @@ using System.Collections.Generic; @@ -20,6 +20,8 @@ using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.IL;
using ICSharpCode.Decompiler.Metadata;
namespace ILSpy.Metadata.CorTables
@ -64,9 +66,27 @@ namespace ILSpy.Metadata.CorTables @@ -64,9 +66,27 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Owner => MetadataTokens.GetToken(genericParamConstraint.Parameter);
string? ownerTooltip;
public string? OwnerTooltip {
get {
if (ownerTooltip == null)
{
ITextOutput output = new PlainTextOutput();
var p = metadataFile.Metadata.GetGenericParameter(genericParamConstraint.Parameter);
output.Write("parameter " + p.Index + (p.Name.IsNil ? "" : " (" + metadataFile.Metadata.GetString(p.Name) + ")") + " of ");
p.Parent.WriteTo(metadataFile, output, default);
ownerTooltip = output.ToString();
}
return ownerTooltip;
}
}
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Type => MetadataTokens.GetToken(genericParamConstraint.Type);
string? typeTooltip;
public string? TypeTooltip => GenerateTooltip(ref typeTooltip, metadataFile, genericParamConstraint.Type);
public GenericParamConstraintEntry(MetadataFile metadataFile, GenericParameterConstraintHandle handle)
{
this.metadataFile = metadataFile;

10
ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs

@ -66,11 +66,21 @@ namespace ILSpy.Metadata.CorTables @@ -66,11 +66,21 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public GenericParameterAttributes Attributes => genericParam.Attributes;
public object AttributesTooltip => new FlagsTooltip {
FlagGroup.CreateSingleChoiceGroup(typeof(GenericParameterAttributes), "Variance: ", (int)GenericParameterAttributes.VarianceMask, (int)(genericParam.Attributes & GenericParameterAttributes.VarianceMask), new Flag("None (0000)", 0, false), includeAny: false),
FlagGroup.CreateMultipleChoiceGroup(typeof(GenericParameterAttributes), "Special Constraint: ", (int)GenericParameterAttributes.SpecialConstraintMask, (int)(genericParam.Attributes & GenericParameterAttributes.SpecialConstraintMask), includeAll: false),
};
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Owner => MetadataTokens.GetToken(genericParam.Parent);
string? ownerTooltip;
public string? OwnerTooltip => GenerateTooltip(ref ownerTooltip, metadataFile, genericParam.Parent);
public string Name => metadataFile.Metadata.GetString(genericParam.Name);
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(genericParam.Name):X} \"{Name}\"";
public GenericParamEntry(MetadataFile metadataFile, GenericParameterHandle handle)
{
this.metadataFile = metadataFile;

28
ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs

@ -53,9 +53,9 @@ namespace ILSpy.Metadata.CorTables @@ -53,9 +53,9 @@ namespace ILSpy.Metadata.CorTables
uint memberForwardedTag = (uint)(memberForwardedTagSize == 2 ? reader.ReadUInt16() : reader.ReadInt32());
int importNameOffset = stringHandleSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
int importScopeRow = moduleRefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
list.Add(new ImplMapEntry(rid, mappingFlags,
list.Add(new ImplMapEntry(metadataFile, rid, mappingFlags,
MetadataReaderHelpers.FromMemberForwardedTag(memberForwardedTag),
metadata.GetString(MetadataTokens.StringHandle(importNameOffset)),
MetadataTokens.StringHandle(importNameOffset),
MetadataTokens.ModuleReferenceHandle(importScopeRow)));
}
return list;
@ -71,23 +71,43 @@ namespace ILSpy.Metadata.CorTables @@ -71,23 +71,43 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public MethodImportAttributes MappingFlags { get; }
const MethodImportAttributes otherFlagsMask = ~(MethodImportAttributes.CallingConventionMask | MethodImportAttributes.CharSetMask);
public object MappingFlagsTooltip => new FlagsTooltip {
FlagGroup.CreateSingleChoiceGroup(typeof(MethodImportAttributes), "Character set: ", (int)MethodImportAttributes.CharSetMask, (int)(MappingFlags & MethodImportAttributes.CharSetMask), new Flag("CharSetNotSpec (0000)", 0, false), includeAny: false),
FlagGroup.CreateSingleChoiceGroup(typeof(MethodImportAttributes), "Calling convention: ", (int)MethodImportAttributes.CallingConventionMask, (int)(MappingFlags & MethodImportAttributes.CallingConventionMask), new Flag("Invalid (0000)", 0, false), includeAny: false),
FlagGroup.CreateMultipleChoiceGroup(typeof(MethodImportAttributes), "Flags:", (int)otherFlagsMask, (int)(MappingFlags & otherFlagsMask), includeAll: false),
};
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int MemberForwarded => MetadataTokens.GetToken(memberForwarded);
string? memberForwardedTooltip;
public string? MemberForwardedTooltip => GenerateTooltip(ref memberForwardedTooltip, metadataFile, memberForwarded);
public string ImportName { get; }
public string ImportNameTooltip => $"{MetadataTokens.GetHeapOffset(importNameHandle):X} \"{ImportName}\"";
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int ImportScope => MetadataTokens.GetToken(importScope);
string? importScopeTooltip;
public string? ImportScopeTooltip => GenerateTooltip(ref importScopeTooltip, metadataFile, importScope);
readonly MetadataFile metadataFile;
readonly EntityHandle memberForwarded;
readonly StringHandle importNameHandle;
readonly ModuleReferenceHandle importScope;
public ImplMapEntry(int rid, MethodImportAttributes mappingFlags, EntityHandle memberForwarded, string importName, ModuleReferenceHandle importScope)
public ImplMapEntry(MetadataFile metadataFile, int rid, MethodImportAttributes mappingFlags, EntityHandle memberForwarded, StringHandle importNameHandle, ModuleReferenceHandle importScope)
{
this.metadataFile = metadataFile;
RID = rid;
MappingFlags = mappingFlags;
this.memberForwarded = memberForwarded;
ImportName = importName;
this.importNameHandle = importNameHandle;
ImportName = metadataFile.Metadata.GetString(importNameHandle);
this.importScope = importScope;
}
}

12
ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs

@ -48,7 +48,7 @@ namespace ILSpy.Metadata.CorTables @@ -48,7 +48,7 @@ namespace ILSpy.Metadata.CorTables
{
int classRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
uint interfaceTag = (uint)(interfaceTagSize == 2 ? reader.ReadUInt16() : reader.ReadInt32());
list.Add(new InterfaceImplEntry(rid,
list.Add(new InterfaceImplEntry(metadataFile, rid,
MetadataTokens.TypeDefinitionHandle(classRow),
MetadataReaderHelpers.FromTypeDefOrRefTag(interfaceTag)));
}
@ -65,14 +65,22 @@ namespace ILSpy.Metadata.CorTables @@ -65,14 +65,22 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Class => MetadataTokens.GetToken(@class);
string? classTooltip;
public string? ClassTooltip => GenerateTooltip(ref classTooltip, metadataFile, @class);
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Interface => MetadataTokens.GetToken(@interface);
string? interfaceTooltip;
public string? InterfaceTooltip => GenerateTooltip(ref interfaceTooltip, metadataFile, @interface);
readonly MetadataFile metadataFile;
readonly TypeDefinitionHandle @class;
readonly EntityHandle @interface;
public InterfaceImplEntry(int rid, TypeDefinitionHandle @class, EntityHandle @interface)
public InterfaceImplEntry(MetadataFile metadataFile, int rid, TypeDefinitionHandle @class, EntityHandle @interface)
{
this.metadataFile = metadataFile;
RID = rid;
this.@class = @class;
this.@interface = @interface;

7
ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs

@ -65,11 +65,18 @@ namespace ILSpy.Metadata.CorTables @@ -65,11 +65,18 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public ManifestResourceAttributes Attributes => manifestResource.Attributes;
public object AttributesTooltip => manifestResource.Attributes.ToString();
public string Name => metadataFile.Metadata.GetString(manifestResource.Name);
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(manifestResource.Name):X} \"{Name}\"";
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Implementation => MetadataTokens.GetToken(manifestResource.Implementation);
string? implementationTooltip;
public string? ImplementationTooltip => GenerateTooltip(ref implementationTooltip, metadataFile, manifestResource.Implementation);
public ManifestResourceEntry(MetadataFile metadataFile, ManifestResourceHandle handle)
{
this.metadataFile = metadataFile;

8
ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs

@ -63,11 +63,19 @@ namespace ILSpy.Metadata.CorTables @@ -63,11 +63,19 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Parent => MetadataTokens.GetToken(memberRef.Parent);
string? parentTooltip;
public string? ParentTooltip => GenerateTooltip(ref parentTooltip, metadataFile, memberRef.Parent);
public string Name => metadataFile.Metadata.GetString(memberRef.Name);
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(memberRef.Name):X} \"{Name}\"";
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(memberRef.Signature);
string? signatureTooltip;
public string? SignatureTooltip => GenerateTooltip(ref signatureTooltip, metadataFile, handle);
public MemberRefEntry(MetadataFile metadataFile, MemberReferenceHandle handle)
{
this.metadataFile = metadataFile;

9
ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs

@ -58,12 +58,21 @@ namespace ILSpy.Metadata.CorTables @@ -58,12 +58,21 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Type => MetadataTokens.GetToken(methodImpl.Type);
string? typeTooltip;
public string? TypeTooltip => GenerateTooltip(ref typeTooltip, metadataFile, methodImpl.Type);
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int MethodDeclaration => MetadataTokens.GetToken(methodImpl.MethodDeclaration);
string? methodDeclarationTooltip;
public string? MethodDeclarationTooltip => GenerateTooltip(ref methodDeclarationTooltip, metadataFile, methodImpl.MethodDeclaration);
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int MethodBody => MetadataTokens.GetToken(methodImpl.MethodBody);
string? methodBodyTooltip;
public string? MethodBodyTooltip => GenerateTooltip(ref methodBodyTooltip, metadataFile, methodImpl.MethodBody);
public MethodImplEntry(MetadataFile metadataFile, MethodImplementationHandle handle)
{
this.metadataFile = metadataFile;

14
ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs

@ -41,7 +41,7 @@ namespace ILSpy.Metadata.CorTables @@ -41,7 +41,7 @@ namespace ILSpy.Metadata.CorTables
{
var list = new List<MethodSemanticsEntry>();
foreach (var row in metadataFile.Metadata.GetMethodSemantics())
list.Add(new MethodSemanticsEntry(row.Handle, row.Semantics, row.Method, row.Association));
list.Add(new MethodSemanticsEntry(metadataFile, row.Handle, row.Semantics, row.Method, row.Association));
return list;
}
@ -55,18 +55,28 @@ namespace ILSpy.Metadata.CorTables @@ -55,18 +55,28 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public MethodSemanticsAttributes Semantics { get; }
public string SemanticsTooltip => Semantics.ToString();
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Method => MetadataTokens.GetToken(method);
string? methodTooltip;
public string? MethodTooltip => GenerateTooltip(ref methodTooltip, metadataFile, method);
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Association => MetadataTokens.GetToken(association);
string? associationTooltip;
public string? AssociationTooltip => GenerateTooltip(ref associationTooltip, metadataFile, association);
readonly MetadataFile metadataFile;
readonly Handle handle;
readonly MethodDefinitionHandle method;
readonly EntityHandle association;
public MethodSemanticsEntry(Handle handle, MethodSemanticsAttributes semantics, MethodDefinitionHandle method, EntityHandle association)
public MethodSemanticsEntry(MetadataFile metadataFile, Handle handle, MethodSemanticsAttributes semantics, MethodDefinitionHandle method, EntityHandle association)
{
this.metadataFile = metadataFile;
this.handle = handle;
Semantics = semantics;
this.method = method;

22
ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs

@ -20,6 +20,8 @@ using System.Collections.Generic; @@ -20,6 +20,8 @@ using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Disassembler;
using ICSharpCode.Decompiler.Metadata;
namespace ILSpy.Metadata.CorTables
@ -63,9 +65,29 @@ namespace ILSpy.Metadata.CorTables @@ -63,9 +65,29 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Method => MetadataTokens.GetToken(methodSpec.Method);
string? methodTooltip;
public string? MethodTooltip => GenerateTooltip(ref methodTooltip, metadataFile, methodSpec.Method);
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(methodSpec.Signature);
public string? SignatureTooltip {
get {
ITextOutput output = new PlainTextOutput();
var signature = methodSpec.DecodeSignature(new DisassemblerSignatureTypeProvider(metadataFile, output), default);
bool first = true;
foreach (var type in signature)
{
if (first)
first = false;
else
output.Write(", ");
type(ILNameSyntax.TypeName);
}
return output.ToString();
}
}
public MethodSpecEntry(MetadataFile metadataFile, MethodSpecificationHandle handle)
{
this.metadataFile = metadataFile;

29
ILSpy/Metadata/CorTables/MethodTableTreeNode.cs

@ -65,20 +65,49 @@ namespace ILSpy.Metadata.CorTables @@ -65,20 +65,49 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public MethodAttributes Attributes => methodDef.Attributes;
const MethodAttributes otherFlagsMask = ~(MethodAttributes.MemberAccessMask | MethodAttributes.VtableLayoutMask);
public object AttributesTooltip => new FlagsTooltip {
FlagGroup.CreateSingleChoiceGroup(typeof(MethodAttributes), "Member access: ", (int)MethodAttributes.MemberAccessMask, (int)(methodDef.Attributes & MethodAttributes.MemberAccessMask), new Flag("CompilerControlled (0000)", 0, false), includeAny: false),
FlagGroup.CreateSingleChoiceGroup(typeof(MethodAttributes), "Vtable layout: ", (int)MethodAttributes.VtableLayoutMask, (int)(methodDef.Attributes & MethodAttributes.VtableLayoutMask), new Flag("ReuseSlot (0000)", 0, false), includeAny: false),
FlagGroup.CreateMultipleChoiceGroup(typeof(MethodAttributes), "Flags:", (int)otherFlagsMask, (int)(methodDef.Attributes & otherFlagsMask), includeAll: false),
};
[ColumnInfo("X8")]
public MethodImplAttributes ImplAttributes => methodDef.ImplAttributes;
public object ImplAttributesTooltip => new FlagsTooltip {
FlagGroup.CreateSingleChoiceGroup(typeof(MethodImplAttributes), "Code type: ", (int)MethodImplAttributes.CodeTypeMask, (int)(methodDef.ImplAttributes & MethodImplAttributes.CodeTypeMask), new Flag("IL (0000)", 0, false), includeAny: false),
FlagGroup.CreateSingleChoiceGroup(typeof(MethodImplAttributes), "Managed type: ", (int)MethodImplAttributes.ManagedMask, (int)(methodDef.ImplAttributes & MethodImplAttributes.ManagedMask), new Flag("Managed (0000)", 0, false), includeAny: false),
};
[ColumnInfo("X8")]
public int RVA => methodDef.RelativeVirtualAddress;
public string Name => metadataFile.Metadata.GetString(methodDef.Name);
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(methodDef.Name):X} \"{Name}\"";
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(methodDef.Signature);
string? signatureTooltip;
public string? SignatureTooltip => GenerateTooltip(ref signatureTooltip, metadataFile, handle);
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int ParamList => MetadataTokens.GetToken(methodDef.GetParameters().FirstOrDefault());
string? paramListTooltip;
public string? ParamListTooltip {
get {
var param = methodDef.GetParameters().FirstOrDefault();
if (param.IsNil)
return null;
return GenerateTooltip(ref paramListTooltip, metadataFile, param);
}
}
public MethodDefEntry(MetadataFile metadataFile, MethodDefinitionHandle handle)
{
this.metadataFile = metadataFile;

2
ILSpy/Metadata/CorTables/ModuleRefTableTreeNode.cs

@ -61,6 +61,8 @@ namespace ILSpy.Metadata.CorTables @@ -61,6 +61,8 @@ namespace ILSpy.Metadata.CorTables
public string Name => metadataFile.Metadata.GetString(moduleRef.Name);
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(moduleRef.Name):X} \"{Name}\"";
public ModuleRefEntry(MetadataFile metadataFile, ModuleReferenceHandle handle)
{
this.metadataFile = metadataFile;

8
ILSpy/Metadata/CorTables/ModuleTableTreeNode.cs

@ -59,15 +59,23 @@ namespace ILSpy.Metadata.CorTables @@ -59,15 +59,23 @@ namespace ILSpy.Metadata.CorTables
public string Name => metadataFile.Metadata.GetString(moduleDef.Name);
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(moduleDef.Name):X} \"{Name}\"";
[ColumnInfo("X8")]
public int Mvid => MetadataTokens.GetHeapOffset(moduleDef.Mvid);
public string MvidTooltip => metadataFile.Metadata.GetGuid(moduleDef.Mvid).ToString();
[ColumnInfo("X8")]
public int GenerationId => MetadataTokens.GetHeapOffset(moduleDef.GenerationId);
public string? GenerationIdTooltip => moduleDef.GenerationId.IsNil ? null : metadataFile.Metadata.GetGuid(moduleDef.GenerationId).ToString();
[ColumnInfo("X8")]
public int BaseGenerationId => MetadataTokens.GetHeapOffset(moduleDef.BaseGenerationId);
public string? BaseGenerationIdTooltip => moduleDef.BaseGenerationId.IsNil ? null : metadataFile.Metadata.GetGuid(moduleDef.BaseGenerationId).ToString();
public ModuleEntry(MetadataFile metadataFile, ModuleDefinitionHandle handle)
{
this.metadataFile = metadataFile;

12
ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs

@ -48,7 +48,7 @@ namespace ILSpy.Metadata.CorTables @@ -48,7 +48,7 @@ namespace ILSpy.Metadata.CorTables
{
int nestedRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
int enclosingRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
list.Add(new NestedClassEntry(rid, MetadataTokens.TypeDefinitionHandle(nestedRow), MetadataTokens.TypeDefinitionHandle(enclosingRow)));
list.Add(new NestedClassEntry(metadataFile, rid, MetadataTokens.TypeDefinitionHandle(nestedRow), MetadataTokens.TypeDefinitionHandle(enclosingRow)));
}
return list;
}
@ -63,14 +63,22 @@ namespace ILSpy.Metadata.CorTables @@ -63,14 +63,22 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int NestedClass => MetadataTokens.GetToken(nested);
string? nestedClassTooltip;
public string? NestedClassTooltip => GenerateTooltip(ref nestedClassTooltip, metadataFile, nested);
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int EnclosingClass => MetadataTokens.GetToken(enclosing);
string? enclosingClassTooltip;
public string? EnclosingClassTooltip => GenerateTooltip(ref enclosingClassTooltip, metadataFile, enclosing);
readonly MetadataFile metadataFile;
readonly TypeDefinitionHandle nested;
readonly TypeDefinitionHandle enclosing;
public NestedClassEntry(int rid, TypeDefinitionHandle nested, TypeDefinitionHandle enclosing)
public NestedClassEntry(MetadataFile metadataFile, int rid, TypeDefinitionHandle nested, TypeDefinitionHandle enclosing)
{
this.metadataFile = metadataFile;
RID = rid;
this.nested = nested;
this.enclosing = enclosing;

6
ILSpy/Metadata/CorTables/ParamTableTreeNode.cs

@ -63,10 +63,16 @@ namespace ILSpy.Metadata.CorTables @@ -63,10 +63,16 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public ParameterAttributes Attributes => param.Attributes;
public object AttributesTooltip => new FlagsTooltip {
FlagGroup.CreateMultipleChoiceGroup(typeof(ParameterAttributes), selectedValue: (int)param.Attributes, includeAll: false)
};
public int Sequence => param.SequenceNumber;
public string Name => metadataFile.Metadata.GetString(param.Name);
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(param.Name):X} \"{Name}\"";
public ParamEntry(MetadataFile metadataFile, ParameterHandle handle)
{
this.metadataFile = metadataFile;

12
ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs

@ -48,7 +48,7 @@ namespace ILSpy.Metadata.CorTables @@ -48,7 +48,7 @@ namespace ILSpy.Metadata.CorTables
{
int parentRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
int propertyListRow = propertyDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
list.Add(new PropertyMapEntry(rid, MetadataTokens.TypeDefinitionHandle(parentRow), MetadataTokens.PropertyDefinitionHandle(propertyListRow)));
list.Add(new PropertyMapEntry(metadataFile, rid, MetadataTokens.TypeDefinitionHandle(parentRow), MetadataTokens.PropertyDefinitionHandle(propertyListRow)));
}
return list;
}
@ -63,14 +63,22 @@ namespace ILSpy.Metadata.CorTables @@ -63,14 +63,22 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Parent => MetadataTokens.GetToken(parent);
string? parentTooltip;
public string? ParentTooltip => GenerateTooltip(ref parentTooltip, metadataFile, parent);
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int PropertyList => MetadataTokens.GetToken(propertyList);
string? propertyListTooltip;
public string? PropertyListTooltip => GenerateTooltip(ref propertyListTooltip, metadataFile, propertyList);
readonly MetadataFile metadataFile;
readonly TypeDefinitionHandle parent;
readonly PropertyDefinitionHandle propertyList;
public PropertyMapEntry(int rid, TypeDefinitionHandle parent, PropertyDefinitionHandle propertyList)
public PropertyMapEntry(MetadataFile metadataFile, int rid, TypeDefinitionHandle parent, PropertyDefinitionHandle propertyList)
{
this.metadataFile = metadataFile;
RID = rid;
this.parent = parent;
this.propertyList = propertyList;

9
ILSpy/Metadata/CorTables/PropertyTableTreeNode.cs

@ -64,11 +64,20 @@ namespace ILSpy.Metadata.CorTables @@ -64,11 +64,20 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public PropertyAttributes Attributes => propertyDef.Attributes;
public object AttributesTooltip => new FlagsTooltip {
FlagGroup.CreateMultipleChoiceGroup(typeof(PropertyAttributes), selectedValue: (int)propertyDef.Attributes, includeAll: false),
};
public string Name => metadataFile.Metadata.GetString(propertyDef.Name);
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(propertyDef.Name):X} \"{Name}\"";
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(propertyDef.Signature);
string? signatureTooltip;
public string? SignatureTooltip => GenerateTooltip(ref signatureTooltip, metadataFile, handle);
public PropertyDefEntry(MetadataFile metadataFile, PropertyDefinitionHandle handle)
{
this.metadataFile = metadataFile;

9
ILSpy/Metadata/CorTables/PtrTableTreeNode.cs

@ -59,7 +59,7 @@ namespace ILSpy.Metadata.CorTables @@ -59,7 +59,7 @@ namespace ILSpy.Metadata.CorTables
for (int rid = 1; rid <= length; rid++)
{
int handleRow = handleSize == 2 ? reader.ReadUInt16() : reader.ReadInt32();
list.Add(new PtrEntry(rid, Kind, MetadataTokens.EntityHandle(((int)referencedTableKind << 24) | handleRow)));
list.Add(new PtrEntry(metadataFile, rid, Kind, MetadataTokens.EntityHandle(((int)referencedTableKind << 24) | handleRow)));
}
return list;
}
@ -74,11 +74,16 @@ namespace ILSpy.Metadata.CorTables @@ -74,11 +74,16 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Handle => MetadataTokens.GetToken(handle);
string? handleTooltip;
public string? HandleTooltip => GenerateTooltip(ref handleTooltip, metadataFile, handle);
readonly MetadataFile metadataFile;
readonly TableIndex kind;
readonly EntityHandle handle;
public PtrEntry(int rid, TableIndex kind, EntityHandle handle)
public PtrEntry(MetadataFile metadataFile, int rid, TableIndex kind, EntityHandle handle)
{
this.metadataFile = metadataFile;
RID = rid;
this.kind = kind;
this.handle = handle;

3
ILSpy/Metadata/CorTables/StandAloneSigTableTreeNode.cs

@ -63,6 +63,9 @@ namespace ILSpy.Metadata.CorTables @@ -63,6 +63,9 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(standaloneSig.Signature);
string? signatureTooltip;
public string? SignatureTooltip => GenerateTooltip(ref signatureTooltip, metadataFile, handle);
public StandAloneSigEntry(MetadataFile metadataFile, StandaloneSignatureHandle handle)
{
this.metadataFile = metadataFile;

60
ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs

@ -22,6 +22,8 @@ using System.Reflection; @@ -22,6 +22,8 @@ using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Disassembler;
using ICSharpCode.Decompiler.Metadata;
namespace ILSpy.Metadata.CorTables
@ -66,19 +68,77 @@ namespace ILSpy.Metadata.CorTables @@ -66,19 +68,77 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8")]
public TypeAttributes Attributes => typeDef.Attributes;
const TypeAttributes otherFlagsMask = ~(TypeAttributes.VisibilityMask | TypeAttributes.LayoutMask | TypeAttributes.ClassSemanticsMask | TypeAttributes.StringFormatMask | TypeAttributes.CustomFormatMask);
public object AttributesTooltip => new FlagsTooltip {
FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Visibility: ", (int)TypeAttributes.VisibilityMask, (int)(typeDef.Attributes & TypeAttributes.VisibilityMask), new Flag("NotPublic (0000)", 0, false), includeAny: false),
FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Class layout: ", (int)TypeAttributes.LayoutMask, (int)(typeDef.Attributes & TypeAttributes.LayoutMask), new Flag("AutoLayout (0000)", 0, false), includeAny: false),
FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Class semantics: ", (int)TypeAttributes.ClassSemanticsMask, (int)(typeDef.Attributes & TypeAttributes.ClassSemanticsMask), new Flag("Class (0000)", 0, false), includeAny: false),
FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "String format: ", (int)TypeAttributes.StringFormatMask, (int)(typeDef.Attributes & TypeAttributes.StringFormatMask), new Flag("AnsiClass (0000)", 0, false), includeAny: false),
FlagGroup.CreateSingleChoiceGroup(typeof(TypeAttributes), "Custom format: ", (int)TypeAttributes.CustomFormatMask, (int)(typeDef.Attributes & TypeAttributes.CustomFormatMask), new Flag("Value0 (0000)", 0, false), includeAny: false),
FlagGroup.CreateMultipleChoiceGroup(typeof(TypeAttributes), "Flags:", (int)otherFlagsMask, (int)(typeDef.Attributes & otherFlagsMask), includeAll: false),
};
public string Name => metadataFile.Metadata.GetString(typeDef.Name);
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(typeDef.Name):X} \"{Name}\"";
public string Namespace => metadataFile.Metadata.GetString(typeDef.Namespace);
public string NamespaceTooltip => $"{MetadataTokens.GetHeapOffset(typeDef.Namespace):X} \"{Namespace}\"";
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int BaseType => MetadataTokens.GetToken(typeDef.BaseType);
public string? BaseTypeTooltip {
get {
var output = new PlainTextOutput();
var provider = new DisassemblerSignatureTypeProvider(metadataFile, output);
if (typeDef.BaseType.IsNil)
return null;
switch (typeDef.BaseType.Kind)
{
case HandleKind.TypeDefinition:
provider.GetTypeFromDefinition(metadataFile.Metadata, (TypeDefinitionHandle)typeDef.BaseType, 0)(ILNameSyntax.Signature);
return output.ToString();
case HandleKind.TypeReference:
provider.GetTypeFromReference(metadataFile.Metadata, (TypeReferenceHandle)typeDef.BaseType, 0)(ILNameSyntax.Signature);
return output.ToString();
case HandleKind.TypeSpecification:
provider.GetTypeFromSpecification(metadataFile.Metadata, new MetadataGenericContext(default(TypeDefinitionHandle), metadataFile.Metadata), (TypeSpecificationHandle)typeDef.BaseType, 0)(ILNameSyntax.Signature);
return output.ToString();
default:
return null;
}
}
}
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int FieldList => MetadataTokens.GetToken(typeDef.GetFields().FirstOrDefault());
string? fieldListTooltip;
public string? FieldListTooltip {
get {
var @field = typeDef.GetFields().FirstOrDefault();
if (@field.IsNil)
return null;
return GenerateTooltip(ref fieldListTooltip, metadataFile, @field);
}
}
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int MethodList => MetadataTokens.GetToken(typeDef.GetMethods().FirstOrDefault());
string? methodListTooltip;
public string? MethodListTooltip {
get {
var method = typeDef.GetMethods().FirstOrDefault();
if (method.IsNil)
return null;
return GenerateTooltip(ref methodListTooltip, metadataFile, method);
}
}
public TypeDefEntry(MetadataFile metadataFile, TypeDefinitionHandle handle)
{
this.metadataFile = metadataFile;

7
ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs

@ -63,10 +63,17 @@ namespace ILSpy.Metadata.CorTables @@ -63,10 +63,17 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int ResolutionScope => MetadataTokens.GetToken(typeRef.ResolutionScope);
string? resolutionScopeTooltip;
public string? ResolutionScopeTooltip => GenerateTooltip(ref resolutionScopeTooltip, metadataFile, typeRef.ResolutionScope);
public string Name => metadataFile.Metadata.GetString(typeRef.Name);
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(typeRef.Name):X} \"{Name}\"";
public string Namespace => metadataFile.Metadata.GetString(typeRef.Namespace);
public string NamespaceTooltip => $"{MetadataTokens.GetHeapOffset(typeRef.Namespace):X} \"{Namespace}\"";
public TypeRefEntry(MetadataFile metadataFile, TypeReferenceHandle handle)
{
this.metadataFile = metadataFile;

10
ILSpy/Metadata/CorTables/TypeSpecTableTreeNode.cs

@ -20,6 +20,8 @@ using System.Collections.Generic; @@ -20,6 +20,8 @@ using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Disassembler;
using ICSharpCode.Decompiler.Metadata;
namespace ILSpy.Metadata.CorTables
@ -63,6 +65,14 @@ namespace ILSpy.Metadata.CorTables @@ -63,6 +65,14 @@ namespace ILSpy.Metadata.CorTables
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(typeSpec.Signature);
public string? SignatureTooltip {
get {
ITextOutput output = new PlainTextOutput();
typeSpec.DecodeSignature(new DisassemblerSignatureTypeProvider(metadataFile, output), default)(ILNameSyntax.Signature);
return output.ToString();
}
}
public TypeSpecEntry(MetadataFile metadataFile, TypeSpecificationHandle handle)
{
this.metadataFile = metadataFile;

11
ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs

@ -60,12 +60,23 @@ namespace ILSpy.Metadata.DebugTables @@ -60,12 +60,23 @@ namespace ILSpy.Metadata.DebugTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Parent => MetadataTokens.GetToken(debugInfo.Parent);
string? parentTooltip;
public string? ParentTooltip => GenerateTooltip(ref parentTooltip, metadataFile, debugInfo.Parent);
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Kind => MetadataTokens.GetHeapOffset(debugInfo.Kind);
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Value => MetadataTokens.GetHeapOffset(debugInfo.Value);
public string ValueTooltip {
get {
if (debugInfo.Value.IsNil)
return "<nil>";
return metadataFile.Metadata.GetBlobReader(debugInfo.Value).ToHexString();
}
}
public CustomDebugInformationEntry(MetadataFile metadataFile, CustomDebugInformationHandle handle)
{
this.metadataFile = metadataFile;

41
ILSpy/Metadata/DebugTables/DocumentTableTreeNode.cs

@ -16,10 +16,12 @@ @@ -16,10 +16,12 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using ICSharpCode.Decompiler.DebugInfo;
using ICSharpCode.Decompiler.Metadata;
namespace ILSpy.Metadata.DebugTables
@ -57,15 +59,54 @@ namespace ILSpy.Metadata.DebugTables @@ -57,15 +59,54 @@ namespace ILSpy.Metadata.DebugTables
public string Name => metadataFile.Metadata.GetString(document.Name);
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(document.Name):X} \"{Name}\"";
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int HashAlgorithm => MetadataTokens.GetHeapOffset(document.HashAlgorithm);
public string? HashAlgorithmTooltip {
get {
if (document.HashAlgorithm.IsNil)
return null;
Guid guid = metadataFile.Metadata.GetGuid(document.HashAlgorithm);
if (guid == KnownGuids.HashAlgorithmSHA1)
return "SHA1 [ff1816ec-aa5e-4d10-87f7-6f4963833460]";
if (guid == KnownGuids.HashAlgorithmSHA256)
return "SHA256 [8829d00f-11b8-4213-878b-770e8597ac16]";
return $"Unknown [" + guid + "]";
}
}
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Hash => MetadataTokens.GetHeapOffset(document.Hash);
public string? HashTooltip {
get {
if (document.Hash.IsNil)
return null;
System.Collections.Immutable.ImmutableArray<byte> token = metadataFile.Metadata.GetBlobContent(document.Hash);
return token.ToHexString(token.Length);
}
}
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Language => MetadataTokens.GetHeapOffset(document.Language);
public string? LanguageTooltip {
get {
if (document.Language.IsNil)
return null;
Guid guid = metadataFile.Metadata.GetGuid(document.Language);
if (guid == KnownGuids.CSharpLanguageGuid)
return "Visual C# [3f5162f8-07c6-11d3-9053-00c04fa302a1]";
if (guid == KnownGuids.VBLanguageGuid)
return "Visual Basic [3a12d0b8-c26c-11d0-b442-00a0244a1dd2]";
if (guid == KnownGuids.FSharpLanguageGuid)
return "Visual F# [ab4f38c9-b6e6-43ba-be3b-58080b2ccce3]";
return $"Unknown [" + guid + "]";
}
}
public DocumentEntry(MetadataFile metadataFile, DocumentHandle handle)
{
this.metadataFile = metadataFile;

2
ILSpy/Metadata/DebugTables/LocalConstantTableTreeNode.cs

@ -57,6 +57,8 @@ namespace ILSpy.Metadata.DebugTables @@ -57,6 +57,8 @@ namespace ILSpy.Metadata.DebugTables
public string Name => metadataFile.Metadata.GetString(localConst.Name);
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(localConst.Name):X} \"{Name}\"";
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int Signature => MetadataTokens.GetHeapOffset(localConst.Signature);

3
ILSpy/Metadata/DebugTables/LocalScopeTableTreeNode.cs

@ -59,6 +59,9 @@ namespace ILSpy.Metadata.DebugTables @@ -59,6 +59,9 @@ namespace ILSpy.Metadata.DebugTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Method => MetadataTokens.GetToken(localScope.Method);
string? methodTooltip;
public string? MethodTooltip => GenerateTooltip(ref methodTooltip, metadataFile, localScope.Method);
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int ImportScope => MetadataTokens.GetToken(localScope.ImportScope);

6
ILSpy/Metadata/DebugTables/LocalVariableTableTreeNode.cs

@ -58,10 +58,16 @@ namespace ILSpy.Metadata.DebugTables @@ -58,10 +58,16 @@ namespace ILSpy.Metadata.DebugTables
[ColumnInfo("X8")]
public LocalVariableAttributes Attributes => localVar.Attributes;
public object AttributesTooltip => new FlagsTooltip() {
FlagGroup.CreateMultipleChoiceGroup(typeof(LocalVariableAttributes)),
};
public int Index => localVar.Index;
public string Name => metadataFile.Metadata.GetString(localVar.Name);
public string NameTooltip => $"{MetadataTokens.GetHeapOffset(localVar.Name):X} \"{Name}\"";
public LocalVariableEntry(MetadataFile metadataFile, LocalVariableHandle handle)
{
this.metadataFile = metadataFile;

46
ILSpy/Metadata/DebugTables/MethodDebugInformationTableTreeNode.cs

@ -19,7 +19,10 @@ @@ -19,7 +19,10 @@
using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Disassembler;
using ICSharpCode.Decompiler.Metadata;
namespace ILSpy.Metadata.DebugTables
@ -58,12 +61,55 @@ namespace ILSpy.Metadata.DebugTables @@ -58,12 +61,55 @@ namespace ILSpy.Metadata.DebugTables
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int Document => MetadataTokens.GetToken(debugInfo.Document);
public string? DocumentTooltip {
get {
if (debugInfo.Document.IsNil)
return null;
var document = metadataFile.Metadata.GetDocument(debugInfo.Document);
return $"{MetadataTokens.GetHeapOffset(document.Name):X} \"{metadataFile.Metadata.GetString(document.Name)}\"";
}
}
[ColumnInfo("X8", Kind = ColumnKind.HeapOffset)]
public int SequencePoints => MetadataTokens.GetHeapOffset(debugInfo.SequencePointsBlob);
public string? SequencePointsTooltip {
get {
if (debugInfo.SequencePointsBlob.IsNil)
return null;
StringBuilder sb = new StringBuilder();
foreach (var p in debugInfo.GetSequencePoints())
{
sb.AppendLine($"document='{MetadataTokens.GetToken(p.Document):X8}', offset={p.Offset}, start={p.StartLine};{p.StartColumn}, end={p.EndLine};{p.EndColumn}, hidden={p.IsHidden}");
}
return sb.ToString().TrimEnd();
}
}
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int LocalSignature => MetadataTokens.GetToken(debugInfo.LocalSignature);
public string? LocalSignatureTooltip {
get {
if (debugInfo.LocalSignature.IsNil)
return null;
ITextOutput output = new PlainTextOutput();
var context = new MetadataGenericContext(default(TypeDefinitionHandle), metadataFile.Metadata);
StandaloneSignature localSignature = metadataFile.Metadata.GetStandaloneSignature(debugInfo.LocalSignature);
var signatureDecoder = new DisassemblerSignatureTypeProvider(metadataFile, output);
int index = 0;
foreach (var item in localSignature.DecodeLocalSignature(signatureDecoder, context))
{
if (index > 0)
output.WriteLine();
output.Write("[{0}] ", index);
item(ILNameSyntax.Signature);
index++;
}
return output.ToString();
}
}
public MethodDebugInformationEntry(MetadataFile metadataFile, MethodDebugInformationHandle handle)
{
this.metadataFile = metadataFile;

23
ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs

@ -49,29 +49,40 @@ namespace ILSpy.Metadata.DebugTables @@ -49,29 +49,40 @@ namespace ILSpy.Metadata.DebugTables
{
var moveNext = MetadataTokens.MethodDefinitionHandle(methodDefSize == 2 ? reader.ReadInt16() : reader.ReadInt32());
var kickoff = MetadataTokens.MethodDefinitionHandle(methodDefSize == 2 ? reader.ReadInt16() : reader.ReadInt32());
list.Add(new StateMachineMethodEntry(rid, moveNext, kickoff));
list.Add(new StateMachineMethodEntry(metadataFile, rid, moveNext, kickoff));
}
return list;
}
public sealed class StateMachineMethodEntry
{
readonly MetadataFile metadataFile;
readonly MethodDefinitionHandle moveNextMethod;
readonly MethodDefinitionHandle kickoffMethod;
public int RID { get; }
[ColumnInfo("X8")]
public int Token => 0x36000000 + RID;
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int MoveNextMethod { get; }
public int MoveNextMethod => MetadataTokens.GetToken(moveNextMethod);
string? moveNextMethodTooltip;
public string? MoveNextMethodTooltip => GenerateTooltip(ref moveNextMethodTooltip, metadataFile, moveNextMethod);
[ColumnInfo("X8", Kind = ColumnKind.Token)]
public int KickoffMethod { get; }
public int KickoffMethod => MetadataTokens.GetToken(kickoffMethod);
string? kickoffMethodTooltip;
public string? KickoffMethodTooltip => GenerateTooltip(ref kickoffMethodTooltip, metadataFile, kickoffMethod);
public StateMachineMethodEntry(int rid, MethodDefinitionHandle moveNext, MethodDefinitionHandle kickoff)
public StateMachineMethodEntry(MetadataFile metadataFile, int rid, MethodDefinitionHandle moveNext, MethodDefinitionHandle kickoff)
{
this.metadataFile = metadataFile;
RID = rid;
MoveNextMethod = MetadataTokens.GetToken(moveNext);
KickoffMethod = MetadataTokens.GetToken(kickoff);
moveNextMethod = moveNext;
kickoffMethod = kickoff;
}
}
}

184
ILSpy/Metadata/FlagsTooltip.cs

@ -0,0 +1,184 @@ @@ -0,0 +1,184 @@
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Media;
using ICSharpCode.Decompiler.Util;
namespace ILSpy.Metadata
{
/// <summary>
/// Rich cell tooltip for an enum-valued metadata column: it breaks a flags value down into
/// per-bit groups, showing each named flag with a checkbox reflecting whether the bit is set
/// (multiple-choice groups) or the single selected member of a mutually exclusive sub-range
/// (single-choice groups). Entries build one with a collection initializer of
/// <see cref="FlagGroup"/>s; <see cref="MetadataCellTooltip"/> renders it via <see cref="Build"/>.
/// </summary>
public sealed class FlagsTooltip : IEnumerable<FlagGroup>
{
// The (value, flagsType) ctor parameters are unused but kept so entry call sites read the
// same as the value they describe; the groups carry the actual selection state.
public FlagsTooltip(int value = 0, Type? flagsType = null)
{
}
public List<FlagGroup> Groups { get; } = new List<FlagGroup>();
public void Add(FlagGroup group) => Groups.Add(group);
public IEnumerator<FlagGroup> GetEnumerator() => Groups.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => Groups.GetEnumerator();
/// <summary>
/// Materialises the tooltip into an Avalonia control: a vertical stack of group views.
/// </summary>
public Control Build()
{
var panel = new StackPanel { Orientation = Orientation.Vertical };
foreach (var group in Groups)
panel.Children.Add(group.Build());
return panel;
}
}
public readonly struct Flag
{
public string Name { get; }
public int Value { get; }
public bool IsSelected { get; }
public Flag(string name, int value, bool isSelected)
{
this.Name = name;
this.Value = value;
this.IsSelected = isSelected;
}
}
public abstract class FlagGroup
{
public static MultipleChoiceGroup CreateMultipleChoiceGroup(Type flagsType, string? header = null, int mask = -1, int selectedValue = 0, bool includeAll = true)
{
return new MultipleChoiceGroup(GetFlags(flagsType, mask, selectedValue, includeAll ? "<All>" : null)) {
Header = header,
SelectedFlags = selectedValue,
};
}
public static SingleChoiceGroup CreateSingleChoiceGroup(Type flagsType, string? header = null, int mask = -1, int selectedValue = 0, Flag defaultFlag = default, bool includeAny = true)
{
var group = new SingleChoiceGroup(GetFlags(flagsType, mask, selectedValue, includeAny ? "<Any>" : null)) {
Header = header,
};
group.SelectedFlag = group.Flags.SingleOrDefault(f => f.Value == selectedValue);
if (group.SelectedFlag.Name == null)
group.SelectedFlag = defaultFlag;
return group;
}
public static IEnumerable<Flag> GetFlags(Type flagsType, int mask = -1, int selectedValues = 0, string? neutralItem = null)
{
if (neutralItem != null)
yield return new Flag(neutralItem, -1, false);
foreach (var item in flagsType.GetFields(BindingFlags.Static | BindingFlags.Public))
{
if (item.Name.EndsWith("Mask", StringComparison.Ordinal))
continue;
int value = (int)CSharpPrimitiveCast.Cast(TypeCode.Int32, item.GetRawConstantValue(), false);
if ((value & mask) == 0)
continue;
yield return new Flag($"{item.Name} ({value:X4})", value, (selectedValues & value) != 0);
}
}
public string? Header { get; set; }
public IList<Flag> Flags { get; protected set; } = Array.Empty<Flag>();
public abstract Control Build();
private protected static TextBlock? BuildHeader(string? header)
{
if (string.IsNullOrEmpty(header))
return null;
return new TextBlock { Text = header, FontWeight = FontWeight.Bold };
}
}
public sealed class MultipleChoiceGroup : FlagGroup
{
public MultipleChoiceGroup(IEnumerable<Flag> flags)
{
this.Flags = flags.ToList();
}
public int SelectedFlags { get; set; }
public override Control Build()
{
var panel = new StackPanel { Orientation = Orientation.Vertical, Margin = new Thickness(3) };
var header = BuildHeader(Header);
if (header != null)
{
header.Margin = new Thickness(0, 0, 0, 3);
panel.Children.Add(header);
}
foreach (var flag in Flags)
{
panel.Children.Add(new CheckBox {
Content = flag.Name,
IsChecked = flag.IsSelected,
IsHitTestVisible = false,
Margin = new Thickness(3, 1),
});
}
return panel;
}
}
public sealed class SingleChoiceGroup : FlagGroup
{
public SingleChoiceGroup(IEnumerable<Flag> flags)
{
this.Flags = flags.ToList();
}
public Flag SelectedFlag { get; set; }
public override Control Build()
{
var panel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(3), Spacing = 3 };
var header = BuildHeader(Header);
if (header != null)
panel.Children.Add(header);
panel.Children.Add(new TextBlock { Text = SelectedFlag.Name });
return panel;
}
}
}

14
ILSpy/Metadata/MetadataCellTooltip.cs

@ -25,8 +25,9 @@ namespace ILSpy.Metadata @@ -25,8 +25,9 @@ namespace ILSpy.Metadata
/// <summary>
/// Resolves per-cell tooltips for a metadata-table row. Entry classes opt in by exposing
/// a public <c>{ColumnName}Tooltip</c> property — <c>NameTooltip</c>, <c>FlagsTooltip</c>,
/// <c>BaseTypeTooltip</c>, and so on. The resolver returns a stringified rendering, so
/// callers can flow it straight into <c>ToolTip.SetTip</c>.
/// <c>BaseTypeTooltip</c>, and so on. A <see cref="FlagsTooltip"/> value renders as the rich
/// per-bit breakdown control; anything else is stringified, so callers can flow the result
/// straight into <c>ToolTip.SetTip</c>.
/// </summary>
public static class MetadataCellTooltip
{
@ -34,10 +35,11 @@ namespace ILSpy.Metadata @@ -34,10 +35,11 @@ namespace ILSpy.Metadata
/// <summary>
/// Looks up <paramref name="columnName"/><c>Tooltip</c> on <paramref name="item"/>'s
/// runtime type. Returns the property's stringified value, or <see langword="null"/>
/// if the item is null, the tooltip property is absent, or the value is null / blank.
/// runtime type. Returns a rich control for a <see cref="FlagsTooltip"/> value, the
/// stringified value otherwise, or <see langword="null"/> if the item is null, the tooltip
/// property is absent, or the value is null / blank.
/// </summary>
public static string? Resolve(object item, string columnName)
public static object? Resolve(object item, string columnName)
{
if (item is null)
return null;
@ -50,6 +52,8 @@ namespace ILSpy.Metadata @@ -50,6 +52,8 @@ namespace ILSpy.Metadata
try
{ value = prop.GetValue(item); }
catch { return null; }
if (value is FlagsTooltip flags)
return flags.Build();
var s = value?.ToString();
return string.IsNullOrWhiteSpace(s) ? null : s;
}

74
ILSpy/Metadata/MetadataTableTreeNode.cs

@ -19,8 +19,11 @@ @@ -19,8 +19,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.IL;
using ICSharpCode.Decompiler.Metadata;
using ILSpy.TreeNodes;
@ -49,6 +52,77 @@ namespace ILSpy.Metadata @@ -49,6 +52,77 @@ namespace ILSpy.Metadata
}
public override object Icon => Images.Images.MetadataTable;
/// <summary>
/// Builds (and caches into <paramref name="tooltip"/>) a human-readable description of the
/// entity a token column points at, e.g. <c>(AssemblyReference) System.Runtime, ...</c>.
/// Entries expose this from their <c>{Column}Tooltip</c> properties so hovering a token cell
/// shows what it refers to. Returns <see langword="null"/> for a nil handle.
/// </summary>
protected static string? GenerateTooltip(ref string? tooltip, MetadataFile module, EntityHandle handle)
{
if (tooltip == null)
{
if (handle.IsNil)
return null;
ITextOutput output = new PlainTextOutput();
var context = new MetadataGenericContext(default(TypeDefinitionHandle), module.Metadata);
var metadata = module.Metadata;
switch (handle.Kind)
{
case HandleKind.ModuleDefinition:
output.Write(metadata.GetString(metadata.GetModuleDefinition().Name));
output.Write(" (this module)");
break;
case HandleKind.ModuleReference:
ModuleReference moduleReference = metadata.GetModuleReference((ModuleReferenceHandle)handle);
output.Write(metadata.GetString(moduleReference.Name));
break;
case HandleKind.AssemblyReference:
var asmRef = new ICSharpCode.Decompiler.Metadata.AssemblyReference(metadata, (AssemblyReferenceHandle)handle);
output.Write(asmRef.ToString());
break;
case HandleKind.Parameter:
var param = metadata.GetParameter((ParameterHandle)handle);
output.Write(param.SequenceNumber + " - " + metadata.GetString(param.Name));
break;
case HandleKind.EventDefinition:
var @event = metadata.GetEventDefinition((EventDefinitionHandle)handle);
output.Write(metadata.GetString(@event.Name));
break;
case HandleKind.PropertyDefinition:
var prop = metadata.GetPropertyDefinition((PropertyDefinitionHandle)handle);
output.Write(metadata.GetString(prop.Name));
break;
case HandleKind.AssemblyDefinition:
var ad = metadata.GetAssemblyDefinition();
output.Write(metadata.GetString(ad.Name));
output.Write(" (this assembly)");
break;
case HandleKind.AssemblyFile:
var af = metadata.GetAssemblyFile((AssemblyFileHandle)handle);
output.Write(metadata.GetString(af.Name));
break;
case HandleKind.GenericParameter:
var gp = metadata.GetGenericParameter((GenericParameterHandle)handle);
output.Write(metadata.GetString(gp.Name));
break;
case HandleKind.ManifestResource:
var mfr = metadata.GetManifestResource((ManifestResourceHandle)handle);
output.Write(metadata.GetString(mfr.Name));
break;
case HandleKind.Document:
var doc = metadata.GetDocument((DocumentHandle)handle);
output.Write(metadata.GetString(doc.Name));
break;
default:
handle.WriteTo(module, output, context);
break;
}
tooltip = "(" + handle.Kind + ") " + output.ToString();
}
return tooltip;
}
}
/// <summary>

Loading…
Cancel
Save