Skip to content

Commit

Permalink
Merge pull request #421 from Washi1337/feature/is-assignable-to
Browse files Browse the repository at this point in the history
TypeSignature Compatibility and Assignability Rules
  • Loading branch information
Washi1337 authored Mar 19, 2023
2 parents cfda2d4 + bf84256 commit e4781b1
Show file tree
Hide file tree
Showing 19 changed files with 1,124 additions and 8 deletions.
67 changes: 67 additions & 0 deletions docs/dotnet/type-signatures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,70 @@ Below an overview of all factory shortcut methods:
+-------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+
| ``MakeGenericInstanceType(TypeSignature[] typeArguments)`` | Wraps the type in a new ``GenericInstanceTypeSignature`` with the provided type arguments. |
+-------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------+



Comparing Type Signatures
-------------------------

Type signatures can be tested for semantic equivalence using the ``SignatureComparer`` class.
Most use-cases of this class will not require any customization.
In these cases, the default ``SignatureComparer`` can be used:

.. code-block:: csharp
var comparer = SignatureComparer.Default;
However, if you wish to configure the comparer (e.g., for relaxing some of the declaring assembly version comparison rules), it is possible to create a new instance instead:

.. code-block:: csharp
var comparer = new SignatureComparer(SignatureComparisonFlags.AllowNewerVersions);
Once a comparer is obtained, we can test for type equality using any of the overloaded ``Equals`` methods:

.. code-block:: csharp
TypeSignature type1 = ...;
TypeSignature type2 = ...;
if (comparer.Equals(type1, type2))
{
// type1 and type2 are semantically equivalent.
}
The ``SignatureComparer`` class implements various instances of the ``IEqualityComparer<T>`` interface, and as such, it can be used as a comparer for dictionaries and related types:

.. code-block:: csharp
var dictionary = new Dictionary<TypeSignature, TValue>(comparer);
.. note::

The ``SignatureComparer`` class also implements equality comparers for other kinds of metadata, such as field and method descriptors and their signatures.


In some cases, however, exact type equivalence is too strict of a test.
Since .NET facilitates an object oriented environment, many types will inherit or derive from each other, making it difficult to pinpoint exactly which types we would need to compare to test whether two types are compatible with each other.

Section I.8.7 of the ECMA-335 specification defines a set of rules that dictate whether values of a certain type are compatible with or assignable to variables of another type.
These rules are implemented in AsmResolver using the ``IsCompatibleWith`` and ``IsAssignableTo`` methods:

.. code-block:: csharp
if (type1.IsCompatibleWith(type2))
{
// type1 can be converted to type2.
}
.. code-block:: csharp
if (type1.IsAssignableTo(type2))
{
// Values of type1 can be assigned to variables of type2.
}
66 changes: 66 additions & 0 deletions src/AsmResolver.DotNet/Signatures/Types/ArrayBaseTypeSignature.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Collections.Generic;

namespace AsmResolver.DotNet.Signatures.Types
{
/// <summary>
/// Represents a type signature representing an array.
/// </summary>
public abstract class ArrayBaseTypeSignature : TypeSpecificationSignature
{
/// <summary>
/// Initializes an array type signature.
/// </summary>
/// <param name="baseType">The element type of the array.</param>
protected ArrayBaseTypeSignature(TypeSignature baseType)
: base(baseType)
{
}

/// <inheritdoc />
public override bool IsValueType => false;

/// <summary>
/// Gets the number of dimensions this array defines.
/// </summary>
public abstract int Rank
{
get;
}

/// <summary>
/// Obtains the dimensions this array defines.
/// </summary>
/// <returns>The dimensions.</returns>
public abstract IEnumerable<ArrayDimension> GetDimensions();

/// <inheritdoc />
protected override bool IsDirectlyCompatibleWith(TypeSignature other, SignatureComparer comparer)
{
if (base.IsDirectlyCompatibleWith(other, comparer))
return true;

TypeSignature? elementType = null;
if (other is ArrayBaseTypeSignature otherArrayType && Rank == otherArrayType.Rank)
{
// Arrays are only compatible if they have the same rank.
elementType = otherArrayType.BaseType;
}
else if (Rank == 1
&& other is GenericInstanceTypeSignature genericInstanceType
&& genericInstanceType.GenericType.IsTypeOf("System.Collections.Generic", "IList`1"))
{
// Arrays are also compatible with IList<T> if they've only one dimension.
elementType = genericInstanceType.TypeArguments[0];
}

if (elementType is null)
return false;

var v = BaseType.GetUnderlyingType();
var w = elementType.GetUnderlyingType();

return comparer.Equals(v.GetReducedType(), w.GetReducedType())
|| v.IsCompatibleWith(w, comparer);
}
}
}
17 changes: 13 additions & 4 deletions src/AsmResolver.DotNet/Signatures/Types/ArrayTypeSignature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace AsmResolver.DotNet.Signatures.Types
/// <remarks>
/// For simple single-dimension arrays, use <see cref="SzArrayTypeSignature"/> instead.
/// </remarks>
public class ArrayTypeSignature : TypeSpecificationSignature
public class ArrayTypeSignature : ArrayBaseTypeSignature
{
/// <summary>
/// Creates a new array type signature.
Expand Down Expand Up @@ -58,9 +58,6 @@ public ArrayTypeSignature(TypeSignature baseType, params ArrayDimension[] dimens
/// <inheritdoc />
public override string Name => $"{BaseType.Name ?? NullTypeToString}{GetDimensionsString()}";

/// <inheritdoc />
public override bool IsValueType => false;

/// <summary>
/// Gets a collection of dimensions.
/// </summary>
Expand All @@ -69,6 +66,12 @@ public IList<ArrayDimension> Dimensions
get;
}

/// <inheritdoc />
public override int Rank => Dimensions.Count;

/// <inheritdoc />
public override IEnumerable<ArrayDimension> GetDimensions() => Dimensions;

internal new static ArrayTypeSignature FromReader(ref BlobReaderContext context, ref BinaryStreamReader reader)
{
var signature = new ArrayTypeSignature(TypeSignature.FromReader(ref context, ref reader));
Expand Down Expand Up @@ -191,6 +194,12 @@ public bool Validate()
return true;
}

/// <inheritdoc />
public override TypeSignature? GetDirectBaseClass() => Module?.CorLibTypeFactory.CorLibScope
.CreateTypeReference("System", "Array")
.ToTypeSignature(false)
.ImportWith(Module.DefaultImporter);

/// <inheritdoc />
public override TResult AcceptVisitor<TResult>(ITypeSignatureVisitor<TResult> visitor) =>
visitor.VisitArrayType(this);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using AsmResolver.PE.DotNet.Metadata.Tables.Rows;

namespace AsmResolver.DotNet.Signatures.Types
Expand Down Expand Up @@ -25,6 +26,24 @@ public ByReferenceTypeSignature(TypeSignature baseType)
/// <inheritdoc />
public override bool IsValueType => false;

/// <inheritdoc />
public override TypeSignature GetVerificationType()
{
if (Module is null)
throw new InvalidOperationException("Cannot determine verification type of a non-imported type.");

var factory = Module.CorLibTypeFactory;
return BaseType.GetReducedType().ElementType switch
{
ElementType.I1 or ElementType.Boolean => factory.SByte.MakeByReferenceType(),
ElementType.I2 or ElementType.Char => factory.Int16.MakeByReferenceType(),
ElementType.I4 => factory.Int32.MakeByReferenceType(),
ElementType.I8 => factory.Int64.MakeByReferenceType(),
ElementType.I => factory.IntPtr.MakeByReferenceType(),
_ => base.GetVerificationType()
};
}

/// <inheritdoc />
public override TResult AcceptVisitor<TResult>(ITypeSignatureVisitor<TResult> visitor) =>
visitor.VisitByReferenceType(this);
Expand Down
43 changes: 43 additions & 0 deletions src/AsmResolver.DotNet/Signatures/Types/CorLibTypeSignature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,49 @@ public override IResolutionScope? Scope
/// <inheritdoc />
public override ITypeDefOrRef ToTypeDefOrRef() => Type;

/// <inheritdoc />
public override TypeSignature GetReducedType()
{
var factory = Module!.CorLibTypeFactory;
return ElementType switch
{
ElementType.I1 or ElementType.U1 => factory.SByte,
ElementType.I2 or ElementType.U2 => factory.Int16,
ElementType.I4 or ElementType.U4 => factory.Int32,
ElementType.I8 or ElementType.U8 => factory.Int64,
ElementType.I or ElementType.U => factory.IntPtr,
_ => base.GetReducedType()
};
}

/// <inheritdoc />
public override TypeSignature GetVerificationType()
{
var factory = Module!.CorLibTypeFactory;
return GetReducedType().ElementType switch
{
ElementType.I1 or ElementType.Boolean => factory.SByte,
ElementType.I2 or ElementType.Char => factory.Int16,
ElementType.I4 => factory.Int32,
ElementType.I8 => factory.Int64,
ElementType.I => factory.IntPtr,
_ => base.GetVerificationType()
};
}

/// <inheritdoc />
public override TypeSignature GetIntermediateType()
{
var factory = Module!.CorLibTypeFactory;
var verificationType = GetVerificationType();
return verificationType.ElementType switch
{
ElementType.I1 or ElementType.I2 or ElementType.I4 => factory.Int32,
ElementType.R4 or ElementType.R8 => factory.Double, // Technically, this is F.
_ => verificationType
};
}

/// <inheritdoc />
protected override void WriteContents(in BlobSerializationContext context) =>
context.Writer.WriteByte((byte) ElementType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,21 @@ public override string Name
/// <inheritdoc />
public override bool IsValueType => BaseType.IsValueType;

/// <inheritdoc />
public override TypeSignature GetReducedType() => BaseType.GetReducedType();

/// <inheritdoc />
public override TypeSignature GetVerificationType() => BaseType.GetVerificationType();

/// <inheritdoc />
public override TypeSignature GetIntermediateType() => BaseType.GetIntermediateType();

/// <inheritdoc />
public override TypeSignature? GetDirectBaseClass() => BaseType.GetDirectBaseClass();

/// <inheritdoc />
public override TypeSignature StripModifiers() => BaseType.StripModifiers();

/// <inheritdoc />
public override TResult AcceptVisitor<TResult>(ITypeSignatureVisitor<TResult> visitor) =>
visitor.VisitCustomModifierType(this);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Linq;
using AsmResolver.PE.DotNet.Metadata.Tables.Rows;

namespace AsmResolver.DotNet.Signatures.Types
Expand Down Expand Up @@ -52,6 +51,29 @@ public MethodSignature Signature
/// <inheritdoc />
public override bool IsImportedInModule(ModuleDefinition module) => Signature.IsImportedInModule(module);

/// <inheritdoc />
protected override bool IsDirectlyCompatibleWith(TypeSignature other, SignatureComparer comparer)
{
if (base.IsDirectlyCompatibleWith(other, comparer))
return true;

if (other is not FunctionPointerTypeSignature {Signature: { } otherSignature}
|| Signature.GenericParameterCount != otherSignature.GenericParameterCount
|| Signature.ParameterTypes.Count != otherSignature.ParameterTypes.Count
|| !Signature.ReturnType.IsAssignableTo(otherSignature.ReturnType, comparer))
{
return false;
}

for (int i = 0; i < Signature.ParameterTypes.Count; i++)
{
if (!Signature.ParameterTypes[i].IsAssignableTo(otherSignature.ParameterTypes[i], comparer))
return false;
}

return true;
}

/// <inheritdoc />
protected override void WriteContents(in BlobSerializationContext context)
{
Expand Down
Loading

0 comments on commit e4781b1

Please sign in to comment.