// 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
{
///
/// Contains logic that determines whether an implicit conversion exists between two types.
///
///
/// This class is thread-safe.
///
public sealed class CSharpConversions
{
readonly ConcurrentDictionary implicitConversionCache = new ConcurrentDictionary();
readonly ICompilation compilation;
public CSharpConversions(ICompilation compilation)
{
if (compilation == null)
throw new ArgumentNullException(nameof(compilation));
this.compilation = compilation;
}
///
/// Gets the Conversions instance for the specified .
/// This will make use of the context's cache manager to reuse the Conversions instance.
///
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
{
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;
}
///
/// Gets whether the type 'fromType' is convertible to 'toType'
/// using one of the conversions allowed when satisying constraints (§4.4.4)
///
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
///
/// Gets whether there is an identity conversion from to
///
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:
IType toTypeArgument = UnpackGenericArrayInterface(toType);
if (fromArray.Dimensions == 1 && toTypeArgument != null)
{
// array covariance plays a part here as well (string[] is IList