Browse Source

Drop isReferenceType when erasing unknown types

Whether an unresolvable type is a reference type is not a property of the
type but of the metadata that mentioned it: a signature spelling it
`valuetype T` yields false, a bare TypeRef yields null. UnknownType.Equals
compares the flag, so the two spellings of one missing type compared
unequal and EquivalentTypes reported false - the decompiler then emitted a
cast between a type and itself.

Erasing the flag in NormalizeTypeVisitor keeps the relaxation inside the
comparisons that ask for erasure, next to the nullability, modopt and tuple
erasure that are use-site spellings of the same kind. Dropping the term
from UnknownType.Equals instead was measured and rejected: Equals also keys
CSharpConversions' implicit-conversion cache, where merging the two
spellings lets whichever conversion is computed first answer for both,
adding 398 boxing casts across two real-world assemblies.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
pull/4070/head
Siegfried Pammer 3 weeks ago
parent
commit
4bc51e6083
  1. 2
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3729.cs
  2. 16
      ICSharpCode.Decompiler/TypeSystem/Implementation/UnknownType.cs
  3. 7
      ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs

2
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue3729.cs

@ -46,7 +46,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty @@ -46,7 +46,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty
public void TestArrayElemCtor()
{
//IL_000f: Unknown result type (might be due to invalid IL or missing references)
MyStruct[] array = (MyStruct[])(object)new MyStruct[1] {
MyStruct[] array = new MyStruct[1] {
new MyStruct(7)
};
}

16
ICSharpCode.Decompiler/TypeSystem/Implementation/UnknownType.cs

@ -117,6 +117,22 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -117,6 +117,22 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
return new NullabilityAnnotatedType(this, nullability);
}
/// <summary>
/// Returns this type without the knowledge whether it is a reference type.
/// </summary>
/// <remarks>
/// Whether an unresolvable type is a reference type is not a property of the type, but of
/// the metadata that mentioned it: a signature spelling it `valuetype T` yields false, a
/// bare TypeRef yields null. Two such spellings of the same missing type must still compare
/// equal after type erasure, so NormalizeTypeVisitor drops the flag before comparing.
/// </remarks>
internal UnknownType WithoutReferenceTypeKnowledge()
{
if (isReferenceType == null)
return this;
return new UnknownType(fullTypeName);
}
public override int GetHashCode()
{
return (namespaceKnown ? 812571 : 12651) ^ fullTypeName.GetHashCode();

7
ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs

@ -97,6 +97,13 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -97,6 +97,13 @@ namespace ICSharpCode.Decompiler.TypeSystem
}
}
public override IType VisitOtherType(IType type)
{
if (type is UnknownType unknownType)
return unknownType.WithoutReferenceTypeKnowledge();
return base.VisitOtherType(type);
}
public override IType VisitTypeDefinition(ITypeDefinition type)
{
switch (type.KnownTypeCode)

Loading…
Cancel
Save