You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1550 lines
53 KiB
1550 lines
53 KiB
// Copyright (c) 2010-2013 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.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
|
|
using ICSharpCode.Decompiler.Semantics;
|
|
using ICSharpCode.Decompiler.TypeSystem;
|
|
using ICSharpCode.Decompiler.Util;
|
|
|
|
namespace ICSharpCode.Decompiler.CSharp.Resolver
|
|
{
|
|
/// <summary>
|
|
/// Contains logic that determines whether an implicit conversion exists between two types.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This class is thread-safe.
|
|
/// </remarks>
|
|
public sealed class CSharpConversions
|
|
{
|
|
readonly ConcurrentDictionary<TypePair, Conversion> implicitConversionCache = new ConcurrentDictionary<TypePair, Conversion>();
|
|
readonly ICompilation compilation;
|
|
|
|
public CSharpConversions(ICompilation compilation)
|
|
{
|
|
if (compilation == null)
|
|
throw new ArgumentNullException(nameof(compilation));
|
|
this.compilation = compilation;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the Conversions instance for the specified <see cref="ICompilation"/>.
|
|
/// This will make use of the context's cache manager to reuse the Conversions instance.
|
|
/// </summary>
|
|
public static CSharpConversions Get(ICompilation compilation)
|
|
{
|
|
if (compilation == null)
|
|
throw new ArgumentNullException(nameof(compilation));
|
|
CacheManager cache = compilation.CacheManager;
|
|
CSharpConversions operators = (CSharpConversions)cache.GetShared(typeof(CSharpConversions));
|
|
if (operators == null)
|
|
{
|
|
operators = (CSharpConversions)cache.GetOrAddShared(typeof(CSharpConversions), new CSharpConversions(compilation));
|
|
}
|
|
return operators;
|
|
}
|
|
|
|
#region TypePair (for caching)
|
|
struct TypePair : IEquatable<TypePair>
|
|
{
|
|
public readonly IType FromType;
|
|
public readonly IType ToType;
|
|
|
|
public TypePair(IType fromType, IType toType)
|
|
{
|
|
Debug.Assert(fromType != null && toType != null);
|
|
this.FromType = fromType;
|
|
this.ToType = toType;
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
return (obj is TypePair) && Equals((TypePair)obj);
|
|
}
|
|
|
|
public bool Equals(TypePair other)
|
|
{
|
|
return object.Equals(this.FromType, other.FromType) && object.Equals(this.ToType, other.ToType);
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
unchecked
|
|
{
|
|
return 1000000007 * FromType.GetHashCode() + 1000000009 * ToType.GetHashCode();
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region ImplicitConversion
|
|
private Conversion ImplicitConversion(ResolveResult resolveResult, IType toType, bool allowUserDefined, bool allowTuple)
|
|
{
|
|
Conversion c;
|
|
if (resolveResult.IsCompileTimeConstant)
|
|
{
|
|
c = ImplicitEnumerationConversion(resolveResult, toType);
|
|
if (c.IsValid)
|
|
return c;
|
|
if (ImplicitConstantExpressionConversion(resolveResult, toType))
|
|
return Conversion.ImplicitConstantExpressionConversion;
|
|
c = StandardImplicitConversion(resolveResult.Type, toType, allowTuple);
|
|
if (c != Conversion.None)
|
|
return c;
|
|
if (allowUserDefined)
|
|
{
|
|
c = UserDefinedImplicitConversion(resolveResult, resolveResult.Type, toType);
|
|
if (c != Conversion.None)
|
|
return c;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (allowTuple && resolveResult is TupleResolveResult tupleRR)
|
|
{
|
|
c = TupleConversion(tupleRR, toType, isExplicit: false);
|
|
if (c != Conversion.None)
|
|
return c;
|
|
}
|
|
if (resolveResult is ThrowResolveResult)
|
|
{
|
|
return Conversion.ThrowExpressionConversion;
|
|
}
|
|
if (allowUserDefined && allowTuple)
|
|
{
|
|
// if allowUserDefined and allowTuple are true, we might as well use the cache
|
|
c = ImplicitConversion(resolveResult.Type, toType);
|
|
}
|
|
else
|
|
{
|
|
c = ImplicitConversion(resolveResult.Type, toType, allowUserDefined, allowTuple);
|
|
}
|
|
if (c != Conversion.None)
|
|
return c;
|
|
}
|
|
if (resolveResult is InterpolatedStringResolveResult isrr)
|
|
{
|
|
if (toType.IsKnownType(KnownTypeCode.IFormattable) || toType.IsKnownType(KnownTypeCode.FormattableString))
|
|
return Conversion.ImplicitInterpolatedStringConversion;
|
|
}
|
|
if (resolveResult.Type.Kind == TypeKind.Dynamic)
|
|
return Conversion.ImplicitDynamicConversion;
|
|
c = AnonymousFunctionConversion(resolveResult, toType);
|
|
if (c != Conversion.None)
|
|
return c;
|
|
c = MethodGroupConversion(resolveResult, toType);
|
|
return c;
|
|
}
|
|
|
|
private Conversion ImplicitConversion(IType fromType, IType toType, bool allowUserDefined, bool allowTuple)
|
|
{
|
|
// C# 4.0 spec: §6.1
|
|
var c = StandardImplicitConversion(fromType, toType, allowTuple);
|
|
if (c == Conversion.None && allowUserDefined)
|
|
{
|
|
c = UserDefinedImplicitConversion(null, fromType, toType);
|
|
}
|
|
return c;
|
|
}
|
|
|
|
public Conversion ImplicitConversion(ResolveResult resolveResult, IType toType)
|
|
{
|
|
if (resolveResult == null)
|
|
throw new ArgumentNullException(nameof(resolveResult));
|
|
return ImplicitConversion(resolveResult, toType, allowUserDefined: true, allowTuple: true);
|
|
}
|
|
|
|
public Conversion ImplicitConversion(IType fromType, IType toType)
|
|
{
|
|
if (fromType == null)
|
|
throw new ArgumentNullException(nameof(fromType));
|
|
if (toType == null)
|
|
throw new ArgumentNullException(nameof(toType));
|
|
|
|
TypePair pair = new TypePair(fromType, toType);
|
|
if (implicitConversionCache.TryGetValue(pair, out Conversion c))
|
|
return c;
|
|
|
|
c = ImplicitConversion(fromType, toType, allowUserDefined: true, allowTuple: true);
|
|
|
|
implicitConversionCache[pair] = c;
|
|
return c;
|
|
}
|
|
|
|
public Conversion StandardImplicitConversion(IType fromType, IType toType)
|
|
{
|
|
if (fromType == null)
|
|
throw new ArgumentNullException(nameof(fromType));
|
|
if (toType == null)
|
|
throw new ArgumentNullException(nameof(toType));
|
|
return StandardImplicitConversion(fromType, toType, allowTupleConversion: true);
|
|
}
|
|
|
|
Conversion StandardImplicitConversion(IType fromType, IType toType, bool allowTupleConversion)
|
|
{
|
|
// C# 4.0 spec: §6.3.1
|
|
if (IdentityConversion(fromType, toType))
|
|
return Conversion.IdentityConversion;
|
|
if (ImplicitNumericConversion(fromType, toType))
|
|
return Conversion.ImplicitNumericConversion;
|
|
Conversion c = ImplicitNullableConversion(fromType, toType);
|
|
if (c != Conversion.None)
|
|
return c;
|
|
if (NullLiteralConversion(fromType, toType))
|
|
return Conversion.NullLiteralConversion;
|
|
if (ImplicitReferenceConversion(fromType, toType, 0))
|
|
return Conversion.ImplicitReferenceConversion;
|
|
if (IsBoxingConversion(fromType, toType))
|
|
return Conversion.BoxingConversion;
|
|
if (ImplicitTypeParameterConversion(fromType, toType))
|
|
{
|
|
// Implicit type parameter conversions that aren't also
|
|
// reference conversions are considered to be boxing conversions
|
|
return Conversion.BoxingConversion;
|
|
}
|
|
if (ImplicitPointerConversion(fromType, toType))
|
|
return Conversion.ImplicitPointerConversion;
|
|
if (allowTupleConversion)
|
|
{
|
|
c = TupleConversion(fromType, toType, isExplicit: false);
|
|
if (c != Conversion.None)
|
|
return c;
|
|
}
|
|
return Conversion.None;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether the type 'fromType' is convertible to 'toType'
|
|
/// using one of the conversions allowed when satisying constraints (§4.4.4)
|
|
/// </summary>
|
|
public bool IsConstraintConvertible(IType fromType, IType toType)
|
|
{
|
|
if (fromType == null)
|
|
throw new ArgumentNullException(nameof(fromType));
|
|
if (toType == null)
|
|
throw new ArgumentNullException(nameof(toType));
|
|
|
|
if (IdentityConversion(fromType, toType))
|
|
return true;
|
|
if (ImplicitReferenceConversion(fromType, toType, 0))
|
|
return true;
|
|
if (NullableType.IsNullable(fromType))
|
|
{
|
|
// An 'object' constraint still allows nullable value types
|
|
// (object constraints don't exist in C#, but are inserted by DefaultResolvedTypeParameter.DirectBaseTypes)
|
|
if (toType.IsKnownType(KnownTypeCode.Object))
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (IsBoxingConversion(fromType, toType))
|
|
return true;
|
|
}
|
|
if (ImplicitTypeParameterConversion(fromType, toType))
|
|
return true;
|
|
return false;
|
|
}
|
|
#endregion
|
|
|
|
#region ExplicitConversion
|
|
public Conversion ExplicitConversion(ResolveResult resolveResult, IType toType)
|
|
{
|
|
if (resolveResult == null)
|
|
throw new ArgumentNullException(nameof(resolveResult));
|
|
if (toType == null)
|
|
throw new ArgumentNullException(nameof(toType));
|
|
|
|
if (resolveResult.Type.Kind == TypeKind.Dynamic)
|
|
return Conversion.ExplicitDynamicConversion;
|
|
Conversion c = ImplicitConversion(resolveResult, toType, allowUserDefined: false, allowTuple: false);
|
|
if (c != Conversion.None)
|
|
return c;
|
|
if (resolveResult is TupleResolveResult tupleRR)
|
|
{
|
|
c = TupleConversion(tupleRR, toType, isExplicit: true);
|
|
if (c != Conversion.None)
|
|
return c;
|
|
}
|
|
c = ExplicitConversionImpl(resolveResult.Type, toType);
|
|
if (c != Conversion.None)
|
|
return c;
|
|
return UserDefinedExplicitConversion(resolveResult, resolveResult.Type, toType);
|
|
}
|
|
|
|
public Conversion ExplicitConversion(IType fromType, IType toType)
|
|
{
|
|
if (fromType == null)
|
|
throw new ArgumentNullException(nameof(fromType));
|
|
if (toType == null)
|
|
throw new ArgumentNullException(nameof(toType));
|
|
|
|
Conversion c = ImplicitConversion(fromType, toType, allowUserDefined: false, allowTuple: false);
|
|
if (c != Conversion.None)
|
|
return c;
|
|
c = ExplicitConversionImpl(fromType, toType);
|
|
if (c != Conversion.None)
|
|
return c;
|
|
return UserDefinedExplicitConversion(null, fromType, toType);
|
|
}
|
|
|
|
Conversion ExplicitConversionImpl(IType fromType, IType toType)
|
|
{
|
|
// This method is called after we already checked for implicit conversions,
|
|
// so any remaining conversions must be explicit.
|
|
if (AnyNumericConversion(fromType, toType))
|
|
return Conversion.ExplicitNumericConversion;
|
|
if (ExplicitEnumerationConversion(fromType, toType))
|
|
return Conversion.EnumerationConversion(false, false);
|
|
Conversion c = ExplicitNullableConversion(fromType, toType);
|
|
if (c != Conversion.None)
|
|
return c;
|
|
if (ExplicitReferenceConversion(fromType, toType))
|
|
return Conversion.ExplicitReferenceConversion;
|
|
if (UnboxingConversion(fromType, toType))
|
|
return Conversion.UnboxingConversion;
|
|
c = ExplicitTypeParameterConversion(fromType, toType);
|
|
if (c != Conversion.None)
|
|
return c;
|
|
if (ExplicitPointerConversion(fromType, toType))
|
|
return Conversion.ExplicitPointerConversion;
|
|
return TupleConversion(fromType, toType, isExplicit: true);
|
|
}
|
|
#endregion
|
|
|
|
#region Identity Conversion
|
|
/// <summary>
|
|
/// Gets whether there is an identity conversion from <paramref name="fromType"/> to <paramref name="toType"/>
|
|
/// </summary>
|
|
public bool IdentityConversion(IType fromType, IType toType)
|
|
{
|
|
// C# 4.0 spec: §6.1.1
|
|
fromType = fromType.AcceptVisitor(NormalizeTypeVisitor.TypeErasure);
|
|
toType = toType.AcceptVisitor(NormalizeTypeVisitor.TypeErasure);
|
|
return fromType.Equals(toType);
|
|
}
|
|
#endregion
|
|
|
|
#region Numeric Conversions
|
|
static readonly bool[,] implicitNumericConversionLookup = {
|
|
// to: short ushort int uint long ulong
|
|
// from:
|
|
/* char */ { false, true , true , true , true , true },
|
|
/* sbyte */ { true , false, true , false, true , false },
|
|
/* byte */ { true , true , true , true , true , true },
|
|
/* short */ { true , false, true , false, true , false },
|
|
/* ushort */ { false, true , true , true , true , true },
|
|
/* int */ { false, false, true , false, true , false },
|
|
/* uint */ { false, false, false, true , true , true },
|
|
/* long */ { false, false, false, false, true , false },
|
|
/* ulong */ { false, false, false, false, false, true },
|
|
};
|
|
|
|
bool ImplicitNumericConversion(IType fromType, IType toType)
|
|
{
|
|
// C# 4.0 spec: §6.1.2
|
|
|
|
TypeCode from = ReflectionHelper.GetTypeCode(fromType);
|
|
if (from == TypeCode.Empty)
|
|
{
|
|
// When converting from a native-sized integer, treat it as 64-bits
|
|
switch (fromType.Kind)
|
|
{
|
|
case TypeKind.NInt:
|
|
from = TypeCode.Int64;
|
|
break;
|
|
case TypeKind.NUInt:
|
|
from = TypeCode.UInt64;
|
|
break;
|
|
}
|
|
}
|
|
TypeCode to = ReflectionHelper.GetTypeCode(toType);
|
|
if (to == TypeCode.Empty)
|
|
{
|
|
// When converting to a native-sized integer, only 32-bits can be stored safely
|
|
switch (toType.Kind)
|
|
{
|
|
case TypeKind.NInt:
|
|
to = TypeCode.Int32;
|
|
break;
|
|
case TypeKind.NUInt:
|
|
to = TypeCode.UInt32;
|
|
break;
|
|
}
|
|
}
|
|
if (to >= TypeCode.Single && to <= TypeCode.Decimal)
|
|
{
|
|
// Conversions to float/double/decimal exist from all integral types,
|
|
// and there's a conversion from float to double.
|
|
return from >= TypeCode.Char && from <= TypeCode.UInt64
|
|
|| from == TypeCode.Single && to == TypeCode.Double;
|
|
}
|
|
else
|
|
{
|
|
// Conversions to integral types: look at the table
|
|
return from >= TypeCode.Char && from <= TypeCode.UInt64
|
|
&& to >= TypeCode.Int16 && to <= TypeCode.UInt64
|
|
&& implicitNumericConversionLookup[from - TypeCode.Char, to - TypeCode.Int16];
|
|
}
|
|
}
|
|
|
|
bool IsNumericType(IType type)
|
|
{
|
|
switch (type.Kind)
|
|
{
|
|
case TypeKind.NInt:
|
|
case TypeKind.NUInt:
|
|
return true;
|
|
}
|
|
TypeCode c = ReflectionHelper.GetTypeCode(type);
|
|
return c >= TypeCode.Char && c <= TypeCode.Decimal;
|
|
}
|
|
|
|
bool AnyNumericConversion(IType fromType, IType toType)
|
|
{
|
|
// C# 4.0 spec: §6.1.2 + §6.2.1
|
|
return IsNumericType(fromType) && IsNumericType(toType);
|
|
}
|
|
#endregion
|
|
|
|
#region Enumeration Conversions
|
|
Conversion ImplicitEnumerationConversion(ResolveResult rr, IType toType)
|
|
{
|
|
// C# 4.0 spec: §6.1.3
|
|
Debug.Assert(rr.IsCompileTimeConstant);
|
|
TypeCode constantType = ReflectionHelper.GetTypeCode(rr.Type);
|
|
if (constantType >= TypeCode.SByte && constantType <= TypeCode.Decimal && Convert.ToDouble(rr.ConstantValue) == 0)
|
|
{
|
|
if (NullableType.GetUnderlyingType(toType).Kind == TypeKind.Enum)
|
|
{
|
|
return Conversion.EnumerationConversion(true, NullableType.IsNullable(toType));
|
|
}
|
|
}
|
|
return Conversion.None;
|
|
}
|
|
|
|
bool ExplicitEnumerationConversion(IType fromType, IType toType)
|
|
{
|
|
// C# 4.0 spec: §6.2.2
|
|
if (fromType.Kind == TypeKind.Enum)
|
|
{
|
|
return toType.Kind == TypeKind.Enum || IsNumericType(toType);
|
|
}
|
|
else if (IsNumericType(fromType))
|
|
{
|
|
return toType.Kind == TypeKind.Enum;
|
|
}
|
|
return false;
|
|
}
|
|
#endregion
|
|
|
|
#region Nullable Conversions
|
|
Conversion ImplicitNullableConversion(IType fromType, IType toType)
|
|
{
|
|
// C# 4.0 spec: §6.1.4
|
|
if (NullableType.IsNullable(toType))
|
|
{
|
|
IType t = NullableType.GetUnderlyingType(toType);
|
|
IType s = NullableType.GetUnderlyingType(fromType); // might or might not be nullable
|
|
if (IdentityConversion(s, t))
|
|
return Conversion.ImplicitNullableConversion;
|
|
if (ImplicitNumericConversion(s, t))
|
|
return Conversion.ImplicitLiftedNumericConversion;
|
|
}
|
|
return Conversion.None;
|
|
}
|
|
|
|
Conversion ExplicitNullableConversion(IType fromType, IType toType)
|
|
{
|
|
// C# 4.0 spec: §6.1.4
|
|
if (NullableType.IsNullable(toType) || NullableType.IsNullable(fromType))
|
|
{
|
|
IType t = NullableType.GetUnderlyingType(toType);
|
|
IType s = NullableType.GetUnderlyingType(fromType);
|
|
if (IdentityConversion(s, t))
|
|
return Conversion.ExplicitNullableConversion;
|
|
if (AnyNumericConversion(s, t))
|
|
return Conversion.ExplicitLiftedNumericConversion;
|
|
if (ExplicitEnumerationConversion(s, t))
|
|
return Conversion.EnumerationConversion(false, true);
|
|
}
|
|
return Conversion.None;
|
|
}
|
|
#endregion
|
|
|
|
#region Null Literal Conversion
|
|
bool NullLiteralConversion(IType fromType, IType toType)
|
|
{
|
|
// C# 4.0 spec: §6.1.5
|
|
if (fromType.Kind == TypeKind.Null)
|
|
{
|
|
return NullableType.IsNullable(toType) || toType.IsReferenceType == true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Implicit Reference Conversion
|
|
public bool IsImplicitReferenceConversion(IType fromType, IType toType)
|
|
{
|
|
return ImplicitReferenceConversion(fromType, toType, 0);
|
|
}
|
|
|
|
bool ImplicitReferenceConversion(IType fromType, IType toType, int subtypeCheckNestingDepth)
|
|
{
|
|
// C# 4.0 spec: §6.1.6
|
|
|
|
// reference conversions are possible:
|
|
// - if both types are known to be reference types
|
|
// - if both types are type parameters and fromType has a class constraint
|
|
// (ImplicitTypeParameterConversionWithClassConstraintOnlyOnT)
|
|
if (!(fromType.IsReferenceType == true && toType.IsReferenceType != false))
|
|
return false;
|
|
|
|
ArrayType fromArray = fromType as ArrayType;
|
|
if (fromArray != null)
|
|
{
|
|
ArrayType toArray = toType as ArrayType;
|
|
if (toArray != null)
|
|
{
|
|
// array covariance (the broken kind)
|
|
return fromArray.Dimensions == toArray.Dimensions
|
|
&& ImplicitReferenceConversion(fromArray.ElementType, toArray.ElementType, subtypeCheckNestingDepth);
|
|
}
|
|
// conversion from single-dimensional array S[] to IList<T>:
|
|
IType toTypeArgument = UnpackGenericArrayInterface(toType);
|
|
if (fromArray.Dimensions == 1 && toTypeArgument != null)
|
|
{
|
|
// array covariance plays a part here as well (string[] is IList<object>)
|
|
return IdentityConversion(fromArray.ElementType, toTypeArgument)
|
|
|| ImplicitReferenceConversion(fromArray.ElementType, toTypeArgument, subtypeCheckNestingDepth);
|
|
}
|
|
// conversion from any array to System.Array and the interfaces it implements:
|
|
IType systemArray = compilation.FindType(KnownTypeCode.Array);
|
|
return ImplicitReferenceConversion(systemArray, toType, subtypeCheckNestingDepth);
|
|
}
|
|
|
|
// now comes the hard part: traverse the inheritance chain and figure out generics+variance
|
|
return IsSubtypeOf(fromType, toType, subtypeCheckNestingDepth);
|
|
}
|
|
|
|
/// <summary>
|
|
/// For IList{T}, ICollection{T}, IEnumerable{T} and IReadOnlyList{T}, returns T.
|
|
/// Otherwise, returns null.
|
|
/// </summary>
|
|
IType UnpackGenericArrayInterface(IType interfaceType)
|
|
{
|
|
ParameterizedType pt = interfaceType as ParameterizedType;
|
|
if (pt != null)
|
|
{
|
|
switch (pt.GetDefinition()?.KnownTypeCode)
|
|
{
|
|
case KnownTypeCode.IListOfT:
|
|
case KnownTypeCode.ICollectionOfT:
|
|
case KnownTypeCode.IEnumerableOfT:
|
|
case KnownTypeCode.IReadOnlyListOfT:
|
|
return pt.GetTypeArgument(0);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Determines whether s is a subtype of t.
|
|
// Helper method used for ImplicitReferenceConversion, BoxingConversion and ImplicitTypeParameterConversion
|
|
|
|
bool IsSubtypeOf(IType s, IType t, int subtypeCheckNestingDepth)
|
|
{
|
|
// conversion to dynamic + object are always possible
|
|
if (t.Kind == TypeKind.Dynamic || t.IsKnownType(KnownTypeCode.Object))
|
|
return true;
|
|
if (subtypeCheckNestingDepth > 10)
|
|
{
|
|
// Subtyping in C# is undecidable
|
|
// (see "On Decidability of Nominal Subtyping with Variance" by Andrew J. Kennedy and Benjamin C. Pierce),
|
|
// so we'll prevent infinite recursions by putting a limit on the nesting depth of variance conversions.
|
|
|
|
// No real C# code should use generics nested more than 10 levels deep, and even if they do, most of
|
|
// those nestings should not involve variance.
|
|
return false;
|
|
}
|
|
// let GetAllBaseTypes do the work for us
|
|
foreach (IType baseType in s.GetAllBaseTypes())
|
|
{
|
|
if (IdentityOrVarianceConversion(baseType, t, subtypeCheckNestingDepth + 1))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IdentityOrVarianceConversion(IType s, IType t, int subtypeCheckNestingDepth)
|
|
{
|
|
ITypeDefinition def = s.GetDefinition();
|
|
if (def != null)
|
|
{
|
|
if (!def.Equals(t.GetDefinition()))
|
|
return false;
|
|
ParameterizedType ps = s as ParameterizedType;
|
|
ParameterizedType pt = t as ParameterizedType;
|
|
if (ps != null && pt != null)
|
|
{
|
|
// C# 4.0 spec: §13.1.3.2 Variance Conversion
|
|
for (int i = 0; i < def.TypeParameters.Count; i++)
|
|
{
|
|
IType si = ps.GetTypeArgument(i);
|
|
IType ti = pt.GetTypeArgument(i);
|
|
if (IdentityConversion(si, ti))
|
|
continue;
|
|
ITypeParameter xi = def.TypeParameters[i];
|
|
switch (xi.Variance)
|
|
{
|
|
case VarianceModifier.Covariant:
|
|
if (!ImplicitReferenceConversion(si, ti, subtypeCheckNestingDepth))
|
|
return false;
|
|
break;
|
|
case VarianceModifier.Contravariant:
|
|
if (!ImplicitReferenceConversion(ti, si, subtypeCheckNestingDepth))
|
|
return false;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if (ps != null || pt != null)
|
|
{
|
|
return false; // only of of them is parameterized, or counts don't match? -> not valid conversion
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// not type definitions? we still need to check for equal types (e.g. s and t might be type parameters)
|
|
return s.Equals(t);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Explicit Reference Conversion
|
|
bool ExplicitReferenceConversion(IType fromType, IType toType)
|
|
{
|
|
// C# 4.0 spec: §6.2.4
|
|
|
|
// test that the types are reference types:
|
|
if (toType.IsReferenceType != true)
|
|
return false;
|
|
if (fromType.IsReferenceType != true)
|
|
{
|
|
// special case:
|
|
// converting from F to T is a reference conversion where T : class, F
|
|
// (because F actually must be a reference type as well, even though C# doesn't treat it as one)
|
|
if (fromType.Kind == TypeKind.TypeParameter)
|
|
return IsSubtypeOf(toType, fromType, 0);
|
|
return false;
|
|
}
|
|
|
|
if (toType.Kind == TypeKind.Array)
|
|
{
|
|
ArrayType toArray = (ArrayType)toType;
|
|
if (fromType.Kind == TypeKind.Array)
|
|
{
|
|
// Array covariance
|
|
ArrayType fromArray = (ArrayType)fromType;
|
|
if (fromArray.Dimensions != toArray.Dimensions)
|
|
return false;
|
|
return ExplicitReferenceConversion(fromArray.ElementType, toArray.ElementType);
|
|
}
|
|
IType fromTypeArgument = UnpackGenericArrayInterface(fromType);
|
|
if (fromTypeArgument != null && toArray.Dimensions == 1)
|
|
{
|
|
return ExplicitReferenceConversion(fromTypeArgument, toArray.ElementType)
|
|
|| IdentityConversion(fromTypeArgument, toArray.ElementType);
|
|
}
|
|
// Otherwise treat the array like a sealed class - require implicit conversion in the opposite direction
|
|
return IsImplicitReferenceConversion(toType, fromType);
|
|
}
|
|
else if (fromType.Kind == TypeKind.Array)
|
|
{
|
|
ArrayType fromArray = (ArrayType)fromType;
|
|
IType toTypeArgument = UnpackGenericArrayInterface(toType);
|
|
if (toTypeArgument != null && fromArray.Dimensions == 1)
|
|
{
|
|
return ExplicitReferenceConversion(fromArray.ElementType, toTypeArgument);
|
|
}
|
|
// Otherwise treat the array like a sealed class
|
|
return IsImplicitReferenceConversion(fromType, toType);
|
|
}
|
|
else if (fromType.Kind == TypeKind.Delegate && toType.Kind == TypeKind.Delegate)
|
|
{
|
|
ITypeDefinition def = fromType.GetDefinition();
|
|
if (def == null || !def.Equals(toType.GetDefinition()))
|
|
return false;
|
|
ParameterizedType ps = fromType as ParameterizedType;
|
|
ParameterizedType pt = toType as ParameterizedType;
|
|
if (ps == null || pt == null)
|
|
{
|
|
// non-generic delegate - return true for the identity conversion
|
|
return ps == null && pt == null;
|
|
}
|
|
for (int i = 0; i < def.TypeParameters.Count; i++)
|
|
{
|
|
IType si = ps.GetTypeArgument(i);
|
|
IType ti = pt.GetTypeArgument(i);
|
|
if (IdentityConversion(si, ti))
|
|
continue;
|
|
ITypeParameter xi = def.TypeParameters[i];
|
|
switch (xi.Variance)
|
|
{
|
|
case VarianceModifier.Covariant:
|
|
if (!ExplicitReferenceConversion(si, ti))
|
|
return false;
|
|
break;
|
|
case VarianceModifier.Contravariant:
|
|
if (!(si.IsReferenceType == true && ti.IsReferenceType == true))
|
|
return false;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
else if (IsSealedReferenceType(fromType))
|
|
{
|
|
// If the source type is sealed, explicit conversions can't do anything more than implicit ones
|
|
return IsImplicitReferenceConversion(fromType, toType);
|
|
}
|
|
else if (IsSealedReferenceType(toType))
|
|
{
|
|
// The the target type is sealed, there must be an implicit conversion in the opposite direction
|
|
return IsImplicitReferenceConversion(toType, fromType);
|
|
}
|
|
else
|
|
{
|
|
if (fromType.Kind == TypeKind.Interface || toType.Kind == TypeKind.Interface)
|
|
return true;
|
|
else
|
|
return IsImplicitReferenceConversion(toType, fromType)
|
|
|| IsImplicitReferenceConversion(fromType, toType);
|
|
}
|
|
}
|
|
|
|
bool IsSealedReferenceType(IType type)
|
|
{
|
|
TypeKind kind = type.Kind;
|
|
return kind == TypeKind.Class && type.GetDefinition().IsSealed
|
|
|| kind == TypeKind.Delegate;
|
|
}
|
|
#endregion
|
|
|
|
#region Boxing Conversions
|
|
bool IsBoxingConversion(IType fromType, IType toType)
|
|
{
|
|
// C# 4.0 spec: §6.1.7
|
|
fromType = NullableType.GetUnderlyingType(fromType);
|
|
if (fromType.IsReferenceType == false && toType.IsReferenceType == true)
|
|
return IsSubtypeOf(fromType, toType, 0);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether the conversion from fromType to toType is a boxing conversion,
|
|
/// or an implicit conversion involving a type parameter that might be
|
|
/// a boxing conversion when instantiated with a value type.
|
|
/// </summary>
|
|
public bool IsBoxingConversionOrInvolvingTypeParameter(IType fromType, IType toType)
|
|
{
|
|
return IsBoxingConversion(fromType, toType)
|
|
|| ImplicitTypeParameterConversion(fromType, toType);
|
|
}
|
|
|
|
bool UnboxingConversion(IType fromType, IType toType)
|
|
{
|
|
// C# 4.0 spec: §6.2.5
|
|
toType = NullableType.GetUnderlyingType(toType);
|
|
if (fromType.IsReferenceType == true && toType.IsReferenceType == false)
|
|
return IsSubtypeOf(toType, fromType, 0);
|
|
else
|
|
return false;
|
|
}
|
|
#endregion
|
|
|
|
#region Implicit Constant-Expression Conversion
|
|
bool ImplicitConstantExpressionConversion(ResolveResult rr, IType toType)
|
|
{
|
|
if (rr == null || !rr.IsCompileTimeConstant)
|
|
return false;
|
|
// C# 4.0 spec: §6.1.9
|
|
TypeCode fromTypeCode = ReflectionHelper.GetTypeCode(rr.Type);
|
|
toType = NullableType.GetUnderlyingType(toType);
|
|
TypeCode toTypeCode = ReflectionHelper.GetTypeCode(toType);
|
|
if (toType.Kind == TypeKind.NUInt)
|
|
{
|
|
toTypeCode = TypeCode.UInt32;
|
|
}
|
|
if (fromTypeCode == TypeCode.Int64)
|
|
{
|
|
long val = (long)rr.ConstantValue;
|
|
return val >= 0 && toTypeCode == TypeCode.UInt64;
|
|
}
|
|
else if (fromTypeCode == TypeCode.Int32)
|
|
{
|
|
object cv = rr.ConstantValue;
|
|
if (cv == null)
|
|
return false;
|
|
int val = (int)cv;
|
|
switch (toTypeCode)
|
|
{
|
|
case TypeCode.SByte:
|
|
return val >= SByte.MinValue && val <= SByte.MaxValue;
|
|
case TypeCode.Byte:
|
|
return val >= Byte.MinValue && val <= Byte.MaxValue;
|
|
case TypeCode.Int16:
|
|
return val >= Int16.MinValue && val <= Int16.MaxValue;
|
|
case TypeCode.UInt16:
|
|
return val >= UInt16.MinValue && val <= UInt16.MaxValue;
|
|
case TypeCode.UInt32:
|
|
case TypeCode.UInt64:
|
|
return val >= 0;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
#endregion
|
|
|
|
#region Conversions involving type parameters
|
|
/// <summary>
|
|
/// Implicit conversions involving type parameters.
|
|
/// </summary>
|
|
bool ImplicitTypeParameterConversion(IType fromType, IType toType)
|
|
{
|
|
if (fromType.Kind != TypeKind.TypeParameter)
|
|
return false; // not a type parameter
|
|
if (fromType.IsReferenceType == true)
|
|
return false; // already handled by ImplicitReferenceConversion
|
|
return IsSubtypeOf(fromType, toType, 0);
|
|
}
|
|
|
|
Conversion ExplicitTypeParameterConversion(IType fromType, IType toType)
|
|
{
|
|
if (toType.Kind == TypeKind.TypeParameter)
|
|
{
|
|
// Explicit type parameter conversions that aren't also
|
|
// reference conversions are considered to be unboxing conversions
|
|
if (fromType.Kind == TypeKind.Interface || IsSubtypeOf(toType, fromType, 0))
|
|
return Conversion.UnboxingConversion;
|
|
}
|
|
else
|
|
{
|
|
if (fromType.Kind == TypeKind.TypeParameter && toType.Kind == TypeKind.Interface)
|
|
return Conversion.BoxingConversion;
|
|
}
|
|
return Conversion.None;
|
|
}
|
|
#endregion
|
|
|
|
#region Pointer Conversions
|
|
bool ImplicitPointerConversion(IType fromType, IType toType)
|
|
{
|
|
// C# 4.0 spec: §18.4 Pointer conversions
|
|
if (fromType.Kind.IsAnyPointer() && toType is PointerType && toType.ReflectionName == "System.Void*")
|
|
return true;
|
|
if (fromType.Kind == TypeKind.Null && toType.Kind.IsAnyPointer())
|
|
return true;
|
|
if (fromType is FunctionPointerType fromFnPtr && toType is FunctionPointerType toFnPtr
|
|
&& fromFnPtr.CallingConvention == toFnPtr.CallingConvention
|
|
&& fromFnPtr.ParameterTypes.Length == toFnPtr.ParameterTypes.Length)
|
|
{
|
|
// Variance applies to function pointer types
|
|
const int nestingDepth = 0;
|
|
if (!(IdentityConversion(fromFnPtr.ReturnType, toFnPtr.ReturnType)
|
|
|| ImplicitReferenceConversion(fromFnPtr.ReturnType, toFnPtr.ReturnType, nestingDepth)))
|
|
{
|
|
return false;
|
|
}
|
|
foreach (var (fromPT, toPT) in fromFnPtr.ParameterTypes.Zip(toFnPtr.ParameterTypes))
|
|
{
|
|
if (!(IdentityConversion(toPT, fromPT)
|
|
|| ImplicitReferenceConversion(toPT, fromPT, nestingDepth)))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ExplicitPointerConversion(IType fromType, IType toType)
|
|
{
|
|
// C# 4.0 spec: §18.4 Pointer conversions
|
|
if (fromType.Kind.IsAnyPointer())
|
|
{
|
|
return toType.Kind.IsAnyPointer() || IsIntegerType(toType);
|
|
}
|
|
else
|
|
{
|
|
return toType.Kind.IsAnyPointer() && IsIntegerType(fromType);
|
|
}
|
|
}
|
|
|
|
bool IsIntegerType(IType type)
|
|
{
|
|
switch (type.Kind)
|
|
{
|
|
case TypeKind.NInt:
|
|
case TypeKind.NUInt:
|
|
return true;
|
|
}
|
|
TypeCode c = ReflectionHelper.GetTypeCode(type);
|
|
return c >= TypeCode.SByte && c <= TypeCode.UInt64;
|
|
}
|
|
#endregion
|
|
|
|
#region User-Defined Conversions
|
|
/// <summary>
|
|
/// Gets whether type A is encompassed by type B.
|
|
/// </summary>
|
|
bool IsEncompassedBy(IType a, IType b)
|
|
{
|
|
return a.Kind != TypeKind.Interface && b.Kind != TypeKind.Interface && StandardImplicitConversion(a, b).IsValid;
|
|
}
|
|
|
|
bool IsEncompassingOrEncompassedBy(IType a, IType b)
|
|
{
|
|
return a.Kind != TypeKind.Interface && b.Kind != TypeKind.Interface
|
|
&& (StandardImplicitConversion(a, b).IsValid || StandardImplicitConversion(b, a).IsValid);
|
|
}
|
|
|
|
IType FindMostEncompassedType(IEnumerable<IType> candidates)
|
|
{
|
|
IType best = null;
|
|
foreach (var current in candidates)
|
|
{
|
|
if (best == null || IsEncompassedBy(current, best))
|
|
best = current;
|
|
else if (!IsEncompassedBy(best, current))
|
|
return null; // Ambiguous
|
|
}
|
|
return best;
|
|
}
|
|
|
|
IType FindMostEncompassingType(IEnumerable<IType> candidates)
|
|
{
|
|
IType best = null;
|
|
foreach (var current in candidates)
|
|
{
|
|
if (best == null || IsEncompassedBy(best, current))
|
|
best = current;
|
|
else if (!IsEncompassedBy(current, best))
|
|
return null; // Ambiguous
|
|
}
|
|
return best;
|
|
}
|
|
|
|
Conversion SelectOperator(IType mostSpecificSource, IType mostSpecificTarget, IList<OperatorInfo> operators, bool isImplicit, IType source, IType target)
|
|
{
|
|
var selected = operators.Where(op => op.SourceType.Equals(mostSpecificSource) && op.TargetType.Equals(mostSpecificTarget)).ToList();
|
|
if (selected.Count == 0)
|
|
return Conversion.None;
|
|
|
|
if (selected.Count == 1)
|
|
return Conversion.UserDefinedConversion(selected[0].Method, isLifted: selected[0].IsLifted, isImplicit: isImplicit, conversionBeforeUserDefinedOperator: ExplicitConversion(source, mostSpecificSource), conversionAfterUserDefinedOperator: ExplicitConversion(mostSpecificTarget, target));
|
|
|
|
int nNonLifted = selected.Count(s => !s.IsLifted);
|
|
if (nNonLifted == 1)
|
|
{
|
|
var op = selected.First(s => !s.IsLifted);
|
|
return Conversion.UserDefinedConversion(op.Method, isLifted: op.IsLifted, isImplicit: isImplicit, conversionBeforeUserDefinedOperator: ExplicitConversion(source, mostSpecificSource), conversionAfterUserDefinedOperator: ExplicitConversion(mostSpecificTarget, target));
|
|
}
|
|
|
|
return Conversion.UserDefinedConversion(selected[0].Method, isLifted: selected[0].IsLifted, isImplicit: isImplicit, isAmbiguous: true, conversionBeforeUserDefinedOperator: ExplicitConversion(source, mostSpecificSource), conversionAfterUserDefinedOperator: ExplicitConversion(mostSpecificTarget, target));
|
|
}
|
|
|
|
Conversion UserDefinedImplicitConversion(ResolveResult fromResult, IType fromType, IType toType)
|
|
{
|
|
// C# 4.0 spec §6.4.4 User-defined implicit conversions
|
|
var operators = GetApplicableConversionOperators(fromResult, fromType, toType, false);
|
|
|
|
if (operators.Count > 0)
|
|
{
|
|
var mostSpecificSource = operators.Any(op => op.SourceType.Equals(fromType)) ? fromType : FindMostEncompassedType(operators.Select(op => op.SourceType));
|
|
if (mostSpecificSource == null)
|
|
return Conversion.UserDefinedConversion(operators[0].Method, isImplicit: true, isLifted: operators[0].IsLifted, isAmbiguous: true, conversionBeforeUserDefinedOperator: Conversion.None, conversionAfterUserDefinedOperator: Conversion.None);
|
|
var mostSpecificTarget = operators.Any(op => op.TargetType.Equals(toType)) ? toType : FindMostEncompassingType(operators.Select(op => op.TargetType));
|
|
if (mostSpecificTarget == null)
|
|
{
|
|
if (NullableType.IsNullable(toType))
|
|
return UserDefinedImplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType));
|
|
else
|
|
return Conversion.UserDefinedConversion(operators[0].Method, isImplicit: true, isLifted: operators[0].IsLifted, isAmbiguous: true, conversionBeforeUserDefinedOperator: Conversion.None, conversionAfterUserDefinedOperator: Conversion.None);
|
|
}
|
|
|
|
var selected = SelectOperator(mostSpecificSource, mostSpecificTarget, operators, true, fromType, toType);
|
|
if (selected != Conversion.None)
|
|
{
|
|
if (selected.IsLifted && NullableType.IsNullable(toType))
|
|
{
|
|
// Prefer A -> B -> B? over A -> A? -> B?
|
|
var other = UserDefinedImplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType));
|
|
if (other != Conversion.None)
|
|
return other;
|
|
}
|
|
return selected;
|
|
}
|
|
else if (NullableType.IsNullable(toType))
|
|
return UserDefinedImplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType));
|
|
else
|
|
return Conversion.None;
|
|
}
|
|
else
|
|
{
|
|
return Conversion.None;
|
|
}
|
|
}
|
|
|
|
Conversion UserDefinedExplicitConversion(ResolveResult fromResult, IType fromType, IType toType)
|
|
{
|
|
// C# 4.0 spec §6.4.5 User-defined explicit conversions
|
|
var operators = GetApplicableConversionOperators(fromResult, fromType, toType, true);
|
|
if (operators.Count > 0)
|
|
{
|
|
IType mostSpecificSource;
|
|
if (operators.Any(op => op.SourceType.Equals(fromType)))
|
|
{
|
|
mostSpecificSource = fromType;
|
|
}
|
|
else
|
|
{
|
|
var operatorsWithSourceEncompassingFromType = operators.Where(op => IsEncompassedBy(fromType, op.SourceType) || ImplicitConstantExpressionConversion(fromResult, NullableType.GetUnderlyingType(op.SourceType))).ToList();
|
|
if (operatorsWithSourceEncompassingFromType.Any())
|
|
mostSpecificSource = FindMostEncompassedType(operatorsWithSourceEncompassingFromType.Select(op => op.SourceType));
|
|
else
|
|
mostSpecificSource = FindMostEncompassingType(operators.Select(op => op.SourceType));
|
|
}
|
|
if (mostSpecificSource == null)
|
|
return Conversion.UserDefinedConversion(operators[0].Method, isImplicit: false, isLifted: operators[0].IsLifted, isAmbiguous: true, conversionBeforeUserDefinedOperator: Conversion.None, conversionAfterUserDefinedOperator: Conversion.None);
|
|
|
|
IType mostSpecificTarget;
|
|
if (operators.Any(op => op.TargetType.Equals(toType)))
|
|
mostSpecificTarget = toType;
|
|
else if (operators.Any(op => IsEncompassedBy(op.TargetType, toType)))
|
|
mostSpecificTarget = FindMostEncompassingType(operators.Where(op => IsEncompassedBy(op.TargetType, toType)).Select(op => op.TargetType));
|
|
else
|
|
mostSpecificTarget = FindMostEncompassedType(operators.Select(op => op.TargetType));
|
|
if (mostSpecificTarget == null)
|
|
{
|
|
if (NullableType.IsNullable(toType))
|
|
return UserDefinedExplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType));
|
|
else
|
|
return Conversion.UserDefinedConversion(operators[0].Method, isImplicit: false, isLifted: operators[0].IsLifted, isAmbiguous: true, conversionBeforeUserDefinedOperator: Conversion.None, conversionAfterUserDefinedOperator: Conversion.None);
|
|
}
|
|
|
|
var selected = SelectOperator(mostSpecificSource, mostSpecificTarget, operators, false, fromType, toType);
|
|
if (selected != Conversion.None)
|
|
{
|
|
if (selected.IsLifted && NullableType.IsNullable(toType))
|
|
{
|
|
// Prefer A -> B -> B? over A -> A? -> B?
|
|
var other = UserDefinedImplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType));
|
|
if (other != Conversion.None)
|
|
return other;
|
|
}
|
|
return selected;
|
|
}
|
|
else if (NullableType.IsNullable(toType))
|
|
return UserDefinedExplicitConversion(fromResult, fromType, NullableType.GetUnderlyingType(toType));
|
|
else if (NullableType.IsNullable(fromType))
|
|
return UserDefinedExplicitConversion(null, NullableType.GetUnderlyingType(fromType), toType); // A? -> A -> B
|
|
else
|
|
return Conversion.None;
|
|
}
|
|
else
|
|
{
|
|
return Conversion.None;
|
|
}
|
|
}
|
|
|
|
class OperatorInfo
|
|
{
|
|
public readonly IMethod Method;
|
|
public readonly IType SourceType;
|
|
public readonly IType TargetType;
|
|
public readonly bool IsLifted;
|
|
|
|
public OperatorInfo(IMethod method, IType sourceType, IType targetType, bool isLifted)
|
|
{
|
|
this.Method = method;
|
|
this.SourceType = sourceType;
|
|
this.TargetType = targetType;
|
|
this.IsLifted = isLifted;
|
|
}
|
|
}
|
|
|
|
static IType UnderlyingTypeForConversion(IType type)
|
|
{
|
|
if (type.Kind == TypeKind.ByReference)
|
|
{
|
|
type = ((ByReferenceType)type).ElementType;
|
|
}
|
|
return NullableType.GetUnderlyingType(type);
|
|
}
|
|
|
|
List<OperatorInfo> GetApplicableConversionOperators(ResolveResult fromResult, IType fromType, IType toType, bool isExplicit)
|
|
{
|
|
// Find the candidate operators:
|
|
Predicate<IMethod> opFilter;
|
|
if (isExplicit)
|
|
opFilter = m => m.IsStatic && m.IsOperator && (m.Name == "op_Explicit" || m.Name == "op_Implicit") && m.Parameters.Count == 1;
|
|
else
|
|
opFilter = m => m.IsStatic && m.IsOperator && m.Name == "op_Implicit" && m.Parameters.Count == 1;
|
|
|
|
var operators = UnderlyingTypeForConversion(fromType).GetMethods(opFilter)
|
|
.Concat(UnderlyingTypeForConversion(toType).GetMethods(opFilter)).Distinct();
|
|
// Determine whether one of them is applicable:
|
|
List<OperatorInfo> result = new List<OperatorInfo>();
|
|
foreach (IMethod op in operators)
|
|
{
|
|
IType sourceType = op.Parameters[0].Type;
|
|
if (sourceType.Kind == TypeKind.ByReference && op.Parameters[0].IsIn && fromType.Kind != TypeKind.ByReference)
|
|
{
|
|
sourceType = ((ByReferenceType)sourceType).ElementType;
|
|
}
|
|
IType targetType = op.ReturnType;
|
|
// Try if the operator is applicable:
|
|
bool isApplicable;
|
|
if (isExplicit)
|
|
{
|
|
isApplicable = (IsEncompassingOrEncompassedBy(fromType, sourceType) || ImplicitConstantExpressionConversion(fromResult, sourceType))
|
|
&& IsEncompassingOrEncompassedBy(targetType, toType);
|
|
}
|
|
else
|
|
{
|
|
isApplicable = (IsEncompassedBy(fromType, sourceType) || ImplicitConstantExpressionConversion(fromResult, sourceType))
|
|
&& IsEncompassedBy(targetType, toType);
|
|
}
|
|
// Try if the operator is applicable in lifted form:
|
|
if (isApplicable)
|
|
{
|
|
result.Add(new OperatorInfo(op, sourceType, targetType, false));
|
|
}
|
|
if (NullableType.IsNonNullableValueType(sourceType))
|
|
{
|
|
// An operator can be applicable in both lifted and non-lifted form in case of explicit conversions
|
|
IType liftedSourceType = NullableType.Create(compilation, sourceType);
|
|
IType liftedTargetType = NullableType.IsNonNullableValueType(targetType) ? NullableType.Create(compilation, targetType) : targetType;
|
|
if (isExplicit)
|
|
{
|
|
isApplicable = IsEncompassingOrEncompassedBy(fromType, liftedSourceType)
|
|
&& IsEncompassingOrEncompassedBy(liftedTargetType, toType);
|
|
}
|
|
else
|
|
{
|
|
isApplicable = IsEncompassedBy(fromType, liftedSourceType) && IsEncompassedBy(liftedTargetType, toType);
|
|
}
|
|
|
|
if (isApplicable)
|
|
{
|
|
result.Add(new OperatorInfo(op, liftedSourceType, liftedTargetType, true));
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
#endregion
|
|
|
|
#region AnonymousFunctionConversion
|
|
Conversion AnonymousFunctionConversion(ResolveResult resolveResult, IType toType)
|
|
{
|
|
// C# 5.0 spec §6.5 Anonymous function conversions
|
|
LambdaResolveResult f = resolveResult as LambdaResolveResult;
|
|
if (f == null)
|
|
return Conversion.None;
|
|
if (!f.IsAnonymousMethod)
|
|
{
|
|
// It's a lambda, so conversions to expression trees exist
|
|
// (even if the conversion leads to a compile-time error, e.g. for statement lambdas)
|
|
toType = UnpackExpressionTreeType(toType);
|
|
}
|
|
IMethod d = toType.GetDelegateInvokeMethod();
|
|
if (d == null)
|
|
return Conversion.None;
|
|
|
|
IType[] dParamTypes = new IType[d.Parameters.Count];
|
|
for (int i = 0; i < dParamTypes.Length; i++)
|
|
{
|
|
dParamTypes[i] = d.Parameters[i].Type;
|
|
}
|
|
IType dReturnType = d.ReturnType;
|
|
|
|
if (f.HasParameterList)
|
|
{
|
|
// If F contains an anonymous-function-signature, then D and F have the same number of parameters.
|
|
if (d.Parameters.Count != f.Parameters.Count)
|
|
return Conversion.None;
|
|
|
|
if (f.IsImplicitlyTyped)
|
|
{
|
|
// If F has an implicitly typed parameter list, D has no ref or out parameters.
|
|
foreach (IParameter p in d.Parameters)
|
|
{
|
|
if (p.ReferenceKind != ReferenceKind.None)
|
|
return Conversion.None;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If F has an explicitly typed parameter list, each parameter in D has the same type
|
|
// and modifiers as the corresponding parameter in F.
|
|
for (int i = 0; i < f.Parameters.Count; i++)
|
|
{
|
|
IParameter pD = d.Parameters[i];
|
|
IParameter pF = f.Parameters[i];
|
|
if (pD.ReferenceKind != pF.ReferenceKind)
|
|
return Conversion.None;
|
|
if (!IdentityConversion(dParamTypes[i], pF.Type))
|
|
return Conversion.None;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If F does not contain an anonymous-function-signature, then D may have zero or more parameters of any
|
|
// type, as long as no parameter of D has the out parameter modifier.
|
|
foreach (IParameter p in d.Parameters)
|
|
{
|
|
if (p.IsOut)
|
|
return Conversion.None;
|
|
}
|
|
}
|
|
|
|
return f.IsValid(dParamTypes, dReturnType, this);
|
|
}
|
|
|
|
static IType UnpackExpressionTreeType(IType type)
|
|
{
|
|
ParameterizedType pt = type as ParameterizedType;
|
|
if (pt != null && pt.TypeParameterCount == 1 && pt.Name == "Expression" && pt.Namespace == "System.Linq.Expressions")
|
|
{
|
|
return pt.GetTypeArgument(0);
|
|
}
|
|
else
|
|
{
|
|
return type;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region MethodGroupConversion
|
|
Conversion MethodGroupConversion(ResolveResult resolveResult, IType toType)
|
|
{
|
|
// C# 4.0 spec §6.6 Method group conversions
|
|
MethodGroupResolveResult rr = resolveResult as MethodGroupResolveResult;
|
|
if (rr == null)
|
|
return Conversion.None;
|
|
IMethod invoke = toType.GetDelegateInvokeMethod();
|
|
if (invoke == null)
|
|
return Conversion.None;
|
|
|
|
ResolveResult[] args = new ResolveResult[invoke.Parameters.Count];
|
|
for (int i = 0; i < args.Length; i++)
|
|
{
|
|
IParameter param = invoke.Parameters[i];
|
|
IType parameterType = param.Type;
|
|
if (param.ReferenceKind != ReferenceKind.None && parameterType.Kind == TypeKind.ByReference)
|
|
{
|
|
parameterType = ((ByReferenceType)parameterType).ElementType;
|
|
args[i] = new ByReferenceResolveResult(parameterType, param.ReferenceKind);
|
|
}
|
|
else
|
|
{
|
|
args[i] = new ResolveResult(parameterType);
|
|
}
|
|
}
|
|
var or = rr.PerformOverloadResolution(
|
|
compilation, args,
|
|
allowExpandingParams: false,
|
|
allowOptionalParameters: false,
|
|
allowImplicitIn: false,
|
|
conversions: this
|
|
);
|
|
if (or.FoundApplicableCandidate)
|
|
{
|
|
IMethod method = (IMethod)or.GetBestCandidateWithSubstitutedTypeArguments();
|
|
var thisRR = rr.TargetResult as ThisResolveResult;
|
|
bool isVirtual = method.IsOverridable && !(thisRR != null && thisRR.CausesNonVirtualInvocation);
|
|
bool isValid = !or.IsAmbiguous && IsDelegateCompatible(method, invoke, or.IsExtensionMethodInvocation);
|
|
bool delegateCapturesFirstArgument = or.IsExtensionMethodInvocation || !method.IsStatic;
|
|
if (isValid)
|
|
return Conversion.MethodGroupConversion(method, isVirtual, delegateCapturesFirstArgument);
|
|
else
|
|
return Conversion.InvalidMethodGroupConversion(method, isVirtual, delegateCapturesFirstArgument);
|
|
}
|
|
else
|
|
{
|
|
return Conversion.None;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether a <paramref name="method"/> is compatible with a delegate type.
|
|
/// §15.2 Delegate compatibility
|
|
/// </summary>
|
|
/// <param name="method">The method to test for compatibility</param>
|
|
/// <param name="delegateType">The delegate type</param>
|
|
public bool IsDelegateCompatible(IMethod method, IType delegateType)
|
|
{
|
|
if (method == null)
|
|
throw new ArgumentNullException(nameof(method));
|
|
if (delegateType == null)
|
|
throw new ArgumentNullException(nameof(delegateType));
|
|
IMethod invoke = delegateType.GetDelegateInvokeMethod();
|
|
if (invoke == null)
|
|
return false;
|
|
return IsDelegateCompatible(method, invoke, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether a method <paramref name="m"/> is compatible with a delegate type.
|
|
/// §15.2 Delegate compatibility
|
|
/// </summary>
|
|
/// <param name="m">The method to test for compatibility</param>
|
|
/// <param name="invoke">The invoke method of the delegate</param>
|
|
/// <param name="isExtensionMethodInvocation">Gets whether m is accessed using extension method syntax.
|
|
/// If this parameter is true, the first parameter of <paramref name="m"/> will be ignored.</param>
|
|
bool IsDelegateCompatible(IMethod m, IMethod invoke, bool isExtensionMethodInvocation)
|
|
{
|
|
if (m == null)
|
|
throw new ArgumentNullException(nameof(m));
|
|
if (invoke == null)
|
|
throw new ArgumentNullException(nameof(invoke));
|
|
int firstParameterInM = isExtensionMethodInvocation ? 1 : 0;
|
|
if (m.Parameters.Count - firstParameterInM != invoke.Parameters.Count)
|
|
return false;
|
|
for (int i = 0; i < invoke.Parameters.Count; i++)
|
|
{
|
|
var pm = m.Parameters[firstParameterInM + i];
|
|
var pd = invoke.Parameters[i];
|
|
// ret/out/in must match
|
|
if (pm.ReferenceKind != pd.ReferenceKind)
|
|
return false;
|
|
if (pm.ReferenceKind != ReferenceKind.None)
|
|
{
|
|
// ref/out/in parameters must have same types
|
|
if (!pm.Type.Equals(pd.Type))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// non-ref/out parameters must have an identity or reference conversion from pd to pm
|
|
if (!IdentityConversion(pd.Type, pm.Type) && !IsImplicitReferenceConversion(pd.Type, pm.Type))
|
|
return false;
|
|
}
|
|
}
|
|
// check return type compatibility
|
|
return IdentityConversion(m.ReturnType, invoke.ReturnType)
|
|
|| IsImplicitReferenceConversion(m.ReturnType, invoke.ReturnType);
|
|
}
|
|
#endregion
|
|
|
|
#region Tuple Conversion
|
|
Conversion TupleConversion(TupleResolveResult fromRR, IType toType, bool isExplicit)
|
|
{
|
|
var fromElements = fromRR.Elements;
|
|
var toElements = TupleType.GetTupleElementTypes(toType);
|
|
if (toElements.IsDefault || fromElements.Length != toElements.Length)
|
|
return Conversion.None;
|
|
Conversion[] elementConversions = new Conversion[fromElements.Length];
|
|
for (int i = 0; i < elementConversions.Length; i++)
|
|
{
|
|
Conversion c;
|
|
if (isExplicit)
|
|
{
|
|
c = ExplicitConversion(fromElements[i], toElements[i]);
|
|
}
|
|
else
|
|
{
|
|
c = ImplicitConversion(fromElements[i], toElements[i]);
|
|
}
|
|
if (!c.IsValid)
|
|
return Conversion.None;
|
|
elementConversions[i] = c;
|
|
}
|
|
return Conversion.TupleConversion(elementConversions.ToImmutableArray());
|
|
}
|
|
|
|
Conversion TupleConversion(IType fromType, IType toType, bool isExplicit)
|
|
{
|
|
var fromElements = TupleType.GetTupleElementTypes(fromType);
|
|
if (fromElements.IsDefaultOrEmpty)
|
|
return Conversion.None;
|
|
var toElements = TupleType.GetTupleElementTypes(toType);
|
|
if (toElements.IsDefault || fromElements.Length != toElements.Length)
|
|
return Conversion.None;
|
|
Conversion[] elementConversions = new Conversion[fromElements.Length];
|
|
for (int i = 0; i < elementConversions.Length; i++)
|
|
{
|
|
Conversion c;
|
|
if (isExplicit)
|
|
{
|
|
c = ExplicitConversion(fromElements[i], toElements[i]);
|
|
}
|
|
else
|
|
{
|
|
c = ImplicitConversion(fromElements[i], toElements[i]);
|
|
}
|
|
if (!c.IsValid)
|
|
return Conversion.None;
|
|
elementConversions[i] = c;
|
|
}
|
|
return Conversion.TupleConversion(elementConversions.ToImmutableArray());
|
|
}
|
|
#endregion
|
|
|
|
#region BetterConversion
|
|
/// <summary>
|
|
/// Gets the better conversion (C# 4.0 spec, §7.5.3.3)
|
|
/// </summary>
|
|
/// <returns>0 = neither is better; 1 = t1 is better; 2 = t2 is better</returns>
|
|
public int BetterConversion(ResolveResult resolveResult, IType t1, IType t2)
|
|
{
|
|
LambdaResolveResult lambda = resolveResult as LambdaResolveResult;
|
|
if (lambda != null)
|
|
{
|
|
if (!lambda.IsAnonymousMethod)
|
|
{
|
|
t1 = UnpackExpressionTreeType(t1);
|
|
t2 = UnpackExpressionTreeType(t2);
|
|
}
|
|
IMethod m1 = t1.GetDelegateInvokeMethod();
|
|
IMethod m2 = t2.GetDelegateInvokeMethod();
|
|
if (m1 == null || m2 == null)
|
|
return 0;
|
|
if (m1.Parameters.Count != m2.Parameters.Count)
|
|
return 0;
|
|
IType[] parameterTypes = new IType[m1.Parameters.Count];
|
|
for (int i = 0; i < parameterTypes.Length; i++)
|
|
{
|
|
parameterTypes[i] = m1.Parameters[i].Type;
|
|
if (!parameterTypes[i].Equals(m2.Parameters[i].Type))
|
|
return 0;
|
|
}
|
|
if (lambda.HasParameterList && parameterTypes.Length != lambda.Parameters.Count)
|
|
return 0;
|
|
|
|
IType ret1 = m1.ReturnType;
|
|
IType ret2 = m2.ReturnType;
|
|
if (ret1.Kind == TypeKind.Void && ret2.Kind != TypeKind.Void)
|
|
return 2;
|
|
if (ret1.Kind != TypeKind.Void && ret2.Kind == TypeKind.Void)
|
|
return 1;
|
|
|
|
IType inferredRet = lambda.GetInferredReturnType(parameterTypes);
|
|
int r = BetterConversion(inferredRet, ret1, ret2);
|
|
if (r == 0 && lambda.IsAsync)
|
|
{
|
|
ret1 = UnpackTask(ret1);
|
|
ret2 = UnpackTask(ret2);
|
|
inferredRet = UnpackTask(inferredRet);
|
|
if (ret1 != null && ret2 != null && inferredRet != null)
|
|
r = BetterConversion(inferredRet, ret1, ret2);
|
|
}
|
|
return r;
|
|
}
|
|
else
|
|
{
|
|
return BetterConversion(resolveResult.Type, t1, t2);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unpacks the generic Task[T]. Returns null if the input is not Task[T].
|
|
/// </summary>
|
|
static IType UnpackTask(IType type)
|
|
{
|
|
ParameterizedType pt = type as ParameterizedType;
|
|
if (pt != null && pt.TypeParameterCount == 1 && pt.Name == "Task" && pt.Namespace == "System.Threading.Tasks")
|
|
{
|
|
return pt.GetTypeArgument(0);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the better conversion (C# 4.0 spec, §7.5.3.4)
|
|
/// </summary>
|
|
/// <returns>0 = neither is better; 1 = t1 is better; 2 = t2 is better</returns>
|
|
public int BetterConversion(IType s, IType t1, IType t2)
|
|
{
|
|
bool ident1 = IdentityConversion(s, t1);
|
|
bool ident2 = IdentityConversion(s, t2);
|
|
if (ident1 && !ident2)
|
|
return 1;
|
|
if (ident2 && !ident1)
|
|
return 2;
|
|
return BetterConversionTarget(t1, t2);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the better conversion target (C# 4.0 spec, §7.5.3.5)
|
|
/// </summary>
|
|
/// <returns>0 = neither is better; 1 = t1 is better; 2 = t2 is better</returns>
|
|
int BetterConversionTarget(IType t1, IType t2)
|
|
{
|
|
bool t1To2 = ImplicitConversion(t1, t2).IsValid;
|
|
bool t2To1 = ImplicitConversion(t2, t1).IsValid;
|
|
if (t1To2 && !t2To1)
|
|
return 1;
|
|
if (t2To1 && !t1To2)
|
|
return 2;
|
|
TypeCode t1Code = ReflectionHelper.GetTypeCode(t1);
|
|
TypeCode t2Code = ReflectionHelper.GetTypeCode(t2);
|
|
if (IsBetterIntegralType(t1Code, t2Code))
|
|
return 1;
|
|
if (IsBetterIntegralType(t2Code, t1Code))
|
|
return 2;
|
|
return 0;
|
|
}
|
|
|
|
bool IsBetterIntegralType(TypeCode t1, TypeCode t2)
|
|
{
|
|
// signed types are better than unsigned types
|
|
switch (t1)
|
|
{
|
|
case TypeCode.SByte:
|
|
return t2 == TypeCode.Byte || t2 == TypeCode.UInt16 || t2 == TypeCode.UInt32 || t2 == TypeCode.UInt64;
|
|
case TypeCode.Int16:
|
|
return t2 == TypeCode.UInt16 || t2 == TypeCode.UInt32 || t2 == TypeCode.UInt64;
|
|
case TypeCode.Int32:
|
|
return t2 == TypeCode.UInt32 || t2 == TypeCode.UInt64;
|
|
case TypeCode.Int64:
|
|
return t2 == TypeCode.UInt64;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|