|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
internal abstract partial class ConversionsBase
{
private const int MaximumRecursionDepth = 50;
protected readonly AssemblySymbol corLibrary;
protected readonly int currentRecursionDepth;
internal readonly bool IncludeNullability;
/// <summary>
/// An optional clone of this instance with distinct IncludeNullability.
/// Used to avoid unnecessary allocations when calling WithNullability() repeatedly.
/// </summary>
private ConversionsBase _lazyOtherNullability;
protected ConversionsBase(AssemblySymbol corLibrary, int currentRecursionDepth, bool includeNullability, ConversionsBase otherNullabilityOpt)
{
Debug.Assert((object)corLibrary != null);
Debug.Assert(otherNullabilityOpt == null || includeNullability != otherNullabilityOpt.IncludeNullability);
Debug.Assert(otherNullabilityOpt == null || currentRecursionDepth == otherNullabilityOpt.currentRecursionDepth);
Debug.Assert(corLibrary == corLibrary.CorLibrary);
this.corLibrary = corLibrary;
this.currentRecursionDepth = currentRecursionDepth;
IncludeNullability = includeNullability;
_lazyOtherNullability = otherNullabilityOpt;
}
/// <summary>
/// Returns this instance if includeNullability is correct, and returns a
/// cached clone of this instance with distinct IncludeNullability otherwise.
/// </summary>
internal ConversionsBase WithNullability(bool includeNullability)
{
if (IncludeNullability == includeNullability)
{
return this;
}
if (_lazyOtherNullability == null)
{
Interlocked.CompareExchange(ref _lazyOtherNullability, WithNullabilityCore(includeNullability), null);
}
Debug.Assert(_lazyOtherNullability.IncludeNullability == includeNullability);
Debug.Assert(_lazyOtherNullability._lazyOtherNullability == this);
return _lazyOtherNullability;
}
protected abstract ConversionsBase WithNullabilityCore(bool includeNullability);
public abstract Conversion GetMethodGroupDelegateConversion(BoundMethodGroup source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo);
public abstract Conversion GetMethodGroupFunctionPointerConversion(BoundMethodGroup source, FunctionPointerTypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo);
public abstract Conversion GetStackAllocConversion(BoundStackAllocArrayCreation sourceExpression, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo);
protected abstract ConversionsBase CreateInstance(int currentRecursionDepth);
protected abstract Conversion GetInterpolatedStringConversion(BoundExpression source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo);
#nullable enable
protected abstract Conversion GetCollectionExpressionConversion(BoundUnconvertedCollectionExpression source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo);
#nullable disable
protected abstract bool IsAttributeArgumentBinding { get; }
protected abstract bool IsParameterDefaultValueBinding { get; }
internal AssemblySymbol CorLibrary { get { return corLibrary; } }
#nullable enable
/// <summary>
/// Derived types should provide non-null value for proper classification of conversions from expression.
/// </summary>
protected abstract CSharpCompilation? Compilation { get; }
/// <summary>
/// Determines if the source expression is convertible to the destination type via
/// any built-in or user-defined implicit conversion.
/// </summary>
public Conversion ClassifyImplicitConversionFromExpression(BoundExpression sourceExpression, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert(sourceExpression != null);
Debug.Assert(Compilation != null);
Debug.Assert((object)destination != null);
var sourceType = sourceExpression.Type;
//PERF: identity conversion is by far the most common implicit conversion, check for that first
if (sourceType is { } && HasIdentityConversionInternal(sourceType, destination))
{
return Conversion.Identity;
}
Conversion conversion = ClassifyImplicitBuiltInConversionFromExpression(sourceExpression, sourceType, destination, ref useSiteInfo);
if (conversion.Exists)
{
return conversion;
}
if (sourceType is { })
{
// Try using the short-circuit "fast-conversion" path.
Conversion fastConversion = FastClassifyConversion(sourceType, destination);
if (fastConversion.Exists)
{
if (fastConversion.IsImplicit)
{
return fastConversion;
}
}
else
{
conversion = ClassifyImplicitBuiltInConversionSlow(sourceType, destination, ref useSiteInfo);
if (conversion.Exists)
{
return conversion;
}
}
}
else if (sourceExpression.GetFunctionType() is { } sourceFunctionType)
{
if (HasImplicitFunctionTypeConversion(sourceFunctionType, destination, ref useSiteInfo))
{
return Conversion.FunctionType;
}
}
conversion = GetImplicitUserDefinedConversion(sourceExpression, sourceType, destination, ref useSiteInfo);
if (conversion.Exists)
{
return conversion;
}
// The switch expression conversion is "lowest priority", so that if there is a conversion from the expression's
// type it will be preferred over the switch expression conversion. Technically, we would want the language
// specification to say that the switch expression conversion only "exists" if there is no implicit conversion
// from the type, and we accomplish that by making it lowest priority. The same is true for the conditional
// expression conversion.
conversion = GetSwitchExpressionConversion(sourceExpression, destination, ref useSiteInfo);
if (conversion.Exists)
{
return conversion;
}
return GetConditionalExpressionConversion(sourceExpression, destination, ref useSiteInfo);
}
/// <summary>
/// Determines if the source type is convertible to the destination type via
/// any built-in or user-defined implicit conversion.
/// </summary>
public Conversion ClassifyImplicitConversionFromType(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
//PERF: identity conversions are very common, check for that first.
if (HasIdentityConversionInternal(source, destination))
{
return Conversion.Identity;
}
// Try using the short-circuit "fast-conversion" path.
Conversion fastConversion = FastClassifyConversion(source, destination);
if (fastConversion.Exists)
{
return fastConversion.IsImplicit ? fastConversion : Conversion.NoConversion;
}
else
{
Conversion conversion = ClassifyImplicitBuiltInConversionSlow(source, destination, ref useSiteInfo);
if (conversion.Exists)
{
return conversion;
}
}
return GetImplicitUserDefinedConversion(source, destination, ref useSiteInfo);
}
/// <summary>
/// Helper method that calls <see cref="ClassifyImplicitConversionFromType"/> or
/// <see cref="HasImplicitFunctionTypeToFunctionTypeConversion"/> depending on whether the
/// types are <see cref="FunctionTypeSymbol"/> instances.
/// Used by method type inference and best common type only.
/// </summary>
public Conversion ClassifyImplicitConversionFromTypeWhenNeitherOrBothFunctionTypes(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
var sourceFunctionType = source as FunctionTypeSymbol;
var destinationFunctionType = destination as FunctionTypeSymbol;
if (sourceFunctionType is null && destinationFunctionType is null)
{
return ClassifyImplicitConversionFromType(source, destination, ref useSiteInfo);
}
if (sourceFunctionType is { } && destinationFunctionType is { })
{
return HasImplicitFunctionTypeToFunctionTypeConversion(sourceFunctionType, destinationFunctionType, ref useSiteInfo) ?
Conversion.FunctionType :
Conversion.NoConversion;
}
Debug.Assert(false);
return Conversion.NoConversion;
}
#nullable disable
/// <summary>
/// Determines if the source expression of given type is convertible to the destination type via
/// any built-in or user-defined conversion.
///
/// This helper is used in rare cases involving synthesized expressions where we know the type of an expression, but do not have the actual expression.
/// The reason for this helper (as opposed to ClassifyConversionFromType) is that conversions from expressions could be different
/// from conversions from type. For example expressions of dynamic type are implicitly convertable to any type, while dynamic type itself is not.
/// </summary>
public Conversion ClassifyConversionFromExpressionType(TypeSymbol source, TypeSymbol destination, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
// since we are converting from expression, we may have implicit dynamic conversion
if (HasImplicitDynamicConversionFromExpression(source, destination))
{
return Conversion.ImplicitDynamic;
}
return ClassifyConversionFromType(source, destination, isChecked: isChecked, ref useSiteInfo);
}
private static bool TryGetVoidConversion(TypeSymbol source, TypeSymbol destination, out Conversion conversion)
{
var sourceIsVoid = source?.SpecialType == SpecialType.System_Void;
var destIsVoid = destination.SpecialType == SpecialType.System_Void;
// 'void' is not supposed to be able to convert to or from anything, but in practice,
// a lot of code depends on checking whether an expression of type 'void' is convertible to 'void'.
// (e.g. for an expression lambda which returns void).
// Therefore we allow an identity conversion between 'void' and 'void'.
if (sourceIsVoid && destIsVoid)
{
conversion = Conversion.Identity;
return true;
}
// If exactly one of source or destination is of type 'void' then no conversion may exist.
if (sourceIsVoid || destIsVoid)
{
conversion = Conversion.NoConversion;
return true;
}
conversion = default;
return false;
}
/// <summary>
/// Determines if the source expression is convertible to the destination type via
/// any conversion: implicit, explicit, user-defined or built-in.
/// </summary>
/// <remarks>
/// It is rare but possible for a source expression to be convertible to a destination type
/// by both an implicit user-defined conversion and a built-in explicit conversion.
/// In that circumstance, this method classifies the conversion as the implicit conversion or explicit depending on "forCast"
/// </remarks>
public Conversion ClassifyConversionFromExpression(BoundExpression sourceExpression, TypeSymbol destination, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, bool forCast = false)
{
Debug.Assert(sourceExpression != null);
Debug.Assert(Compilation != null);
Debug.Assert((object)destination != null);
if (TryGetVoidConversion(sourceExpression.Type, destination, out var conversion))
{
return conversion;
}
if (forCast)
{
return ClassifyConversionFromExpressionForCast(sourceExpression, destination, isChecked: isChecked, ref useSiteInfo);
}
var result = ClassifyImplicitConversionFromExpression(sourceExpression, destination, ref useSiteInfo);
if (result.Exists)
{
return result;
}
return ClassifyExplicitOnlyConversionFromExpression(sourceExpression, destination, isChecked: isChecked, ref useSiteInfo, forCast: false);
}
/// <summary>
/// Determines if the source type is convertible to the destination type via
/// any conversion: implicit, explicit, user-defined or built-in.
/// </summary>
/// <remarks>
/// It is rare but possible for a source type to be convertible to a destination type
/// by both an implicit user-defined conversion and a built-in explicit conversion.
/// In that circumstance, this method classifies the conversion as the implicit conversion or explicit depending on "forCast"
/// </remarks>
public Conversion ClassifyConversionFromType(TypeSymbol source, TypeSymbol destination, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, bool forCast = false)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
if (TryGetVoidConversion(source, destination, out var voidConversion))
{
return voidConversion;
}
if (forCast)
{
return ClassifyConversionFromTypeForCast(source, destination, isChecked: isChecked, ref useSiteInfo);
}
// Try using the short-circuit "fast-conversion" path.
Conversion fastConversion = FastClassifyConversion(source, destination);
if (fastConversion.Exists)
{
return fastConversion;
}
else
{
Conversion conversion1 = ClassifyImplicitBuiltInConversionSlow(source, destination, ref useSiteInfo);
if (conversion1.Exists)
{
return conversion1;
}
}
Conversion conversion = GetImplicitUserDefinedConversion(source, destination, ref useSiteInfo);
if (conversion.Exists)
{
return conversion;
}
conversion = ClassifyExplicitBuiltInOnlyConversion(source, destination, isChecked: isChecked, ref useSiteInfo, forCast: false);
if (conversion.Exists)
{
return conversion;
}
return GetExplicitUserDefinedConversion(source, destination, isChecked: isChecked, ref useSiteInfo);
}
/// <summary>
/// Determines if the source expression is convertible to the destination type via
/// any conversion: implicit, explicit, user-defined or built-in.
/// </summary>
/// <remarks>
/// It is rare but possible for a source expression to be convertible to a destination type
/// by both an implicit user-defined conversion and a built-in explicit conversion.
/// In that circumstance, this method classifies the conversion as the built-in conversion.
///
/// An implicit conversion exists from an expression of a dynamic type to any type.
/// An explicit conversion exists from a dynamic type to any type.
/// When casting we prefer the explicit conversion.
/// </remarks>
private Conversion ClassifyConversionFromExpressionForCast(BoundExpression source, TypeSymbol destination, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert(source != null);
Debug.Assert(Compilation != null);
Debug.Assert((object)destination != null);
Conversion implicitConversion = ClassifyImplicitConversionFromExpression(source, destination, ref useSiteInfo);
if (implicitConversion.Exists && !ExplicitConversionMayDifferFromImplicit(implicitConversion))
{
return implicitConversion;
}
Conversion explicitConversion = ClassifyExplicitOnlyConversionFromExpression(source, destination, isChecked: isChecked, ref useSiteInfo, forCast: true);
if (explicitConversion.Exists)
{
return explicitConversion;
}
// It is possible for a user-defined conversion to be unambiguous when considered as
// an implicit conversion and ambiguous when considered as an explicit conversion.
// The native compiler does not check to see if a cast could be successfully bound as
// an unambiguous user-defined implicit conversion; it goes right to the ambiguous
// user-defined explicit conversion and produces an error. This means that in
// C# 5 it is possible to have:
//
// Y y = new Y();
// Z z1 = y;
//
// succeed but
//
// Z z2 = (Z)y;
//
// fail.
//
// However, there is another interesting wrinkle. It is possible for both
// an implicit user-defined conversion and an explicit user-defined conversion
// to exist and be unambiguous. For example, if there is an implicit conversion
// double-->C and an explicit conversion from int-->C, and the user casts a short
// to C, then both the implicit and explicit conversions are applicable and
// unambiguous. The native compiler in this case prefers the explicit conversion,
// and for backwards compatibility, we match it.
return implicitConversion;
}
/// <summary>
/// Determines if the source type is convertible to the destination type via
/// any conversion: implicit, explicit, user-defined or built-in.
/// </summary>
/// <remarks>
/// It is rare but possible for a source type to be convertible to a destination type
/// by both an implicit user-defined conversion and a built-in explicit conversion.
/// In that circumstance, this method classifies the conversion as the built-in conversion.
/// </remarks>
private Conversion ClassifyConversionFromTypeForCast(TypeSymbol source, TypeSymbol destination, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
// Try using the short-circuit "fast-conversion" path.
Conversion fastConversion = FastClassifyConversion(source, destination);
if (fastConversion.Exists)
{
return fastConversion;
}
Conversion implicitBuiltInConversion = ClassifyImplicitBuiltInConversionSlow(source, destination, ref useSiteInfo);
if (implicitBuiltInConversion.Exists && !ExplicitConversionMayDifferFromImplicit(implicitBuiltInConversion))
{
return implicitBuiltInConversion;
}
Conversion explicitBuiltInConversion = ClassifyExplicitBuiltInOnlyConversion(source, destination, isChecked: isChecked, ref useSiteInfo, forCast: true);
if (explicitBuiltInConversion.Exists)
{
return explicitBuiltInConversion;
}
if (implicitBuiltInConversion.Exists)
{
return implicitBuiltInConversion;
}
// It is possible for a user-defined conversion to be unambiguous when considered as
// an implicit conversion and ambiguous when considered as an explicit conversion.
// The native compiler does not check to see if a cast could be successfully bound as
// an unambiguous user-defined implicit conversion; it goes right to the ambiguous
// user-defined explicit conversion and produces an error. This means that in
// C# 5 it is possible to have:
//
// Y y = new Y();
// Z z1 = y;
//
// succeed but
//
// Z z2 = (Z)y;
//
// fail.
var conversion = GetExplicitUserDefinedConversion(source, destination, isChecked: isChecked, ref useSiteInfo);
if (conversion.Exists)
{
return conversion;
}
return GetImplicitUserDefinedConversion(source, destination, ref useSiteInfo);
}
/// <summary>
/// Attempt a quick classification of builtin conversions. As result of "no conversion"
/// means that there is no built-in conversion, though there still may be a user-defined
/// conversion if compiling against a custom mscorlib.
/// </summary>
public static Conversion FastClassifyConversion(TypeSymbol source, TypeSymbol target)
{
ConversionKind convKind = ConversionEasyOut.ClassifyConversion(source, target);
if (convKind != ConversionKind.ImplicitNullable && convKind != ConversionKind.ExplicitNullable)
{
return Conversion.GetTrivialConversion(convKind);
}
return Conversion.MakeNullableConversion(convKind, FastClassifyConversion(source.StrippedType(), target.StrippedType()));
}
public Conversion ClassifyBuiltInConversion(TypeSymbol source, TypeSymbol destination, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
// Try using the short-circuit "fast-conversion" path.
Conversion fastConversion = FastClassifyConversion(source, destination);
if (fastConversion.Exists)
{
return fastConversion;
}
else
{
Conversion conversion = ClassifyImplicitBuiltInConversionSlow(source, destination, ref useSiteInfo);
if (conversion.Exists)
{
return conversion;
}
}
return ClassifyExplicitBuiltInOnlyConversion(source, destination, isChecked: isChecked, ref useSiteInfo, forCast: false);
}
/// <summary>
/// Determines if the source type is convertible to the destination type via
/// any standard implicit or standard explicit conversion.
/// </summary>
/// <remarks>
/// Not all built-in explicit conversions are standard explicit conversions.
/// </remarks>
public Conversion ClassifyStandardConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
return ClassifyStandardConversion(sourceExpression: null, source, destination, ref useSiteInfo);
}
/// <summary>
/// Determines if the source type is convertible to the destination type via
/// any standard implicit or standard explicit conversion.
/// </summary>
/// <remarks>
/// Not all built-in explicit conversions are standard explicit conversions.
/// </remarks>
public Conversion ClassifyStandardConversion(BoundExpression sourceExpression, TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert(sourceExpression is null || Compilation is not null);
Debug.Assert(sourceExpression != null || (object)source != null);
Debug.Assert((object)destination != null);
// Note that the definition of explicit standard conversion does not include all explicit
// reference conversions! There is a standard implicit reference conversion from
// Action<Object> to Action<Exception>, thanks to contravariance. There is a standard
// implicit reference conversion from Action<Object> to Action<String> for the same reason.
// Therefore there is an explicit reference conversion from Action<Exception> to
// Action<String>; a given Action<Exception> might be an Action<Object>, and hence
// convertible to Action<String>. However, this is not a *standard* explicit conversion. The
// standard explicit conversions are all the standard implicit conversions and their
// opposites. Therefore Action<Object>-->Action<String> and Action<String>-->Action<Object>
// are both standard conversions. But Action<String>-->Action<Exception> is not a standard
// explicit conversion because neither it nor its opposite is a standard implicit
// conversion.
//
// Similarly, there is no standard explicit conversion from double to decimal, because
// there is no standard implicit conversion between the two types.
// SPEC: The standard explicit conversions are all standard implicit conversions plus
// SPEC: the subset of the explicit conversions for which an opposite standard implicit
// SPEC: conversion exists. In other words, if a standard implicit conversion exists from
// SPEC: a type A to a type B, then a standard explicit conversion exists from type A to
// SPEC: type B and from type B to type A.
Conversion conversion = ClassifyStandardImplicitConversion(sourceExpression, source, destination, ref useSiteInfo);
if (conversion.Exists)
{
return conversion;
}
if ((object)source != null)
{
return DeriveStandardExplicitFromOppositeStandardImplicitConversion(source, destination, ref useSiteInfo);
}
return Conversion.NoConversion;
}
// See https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/conversions.md#1042-standard-implicit-conversions:
// "The standard conversions are those pre-defined conversions that can occur as part of a user-defined conversion."
private static bool IsStandardImplicitConversionFromType(ConversionKind kind)
{
switch (kind)
{
case ConversionKind.Identity:
case ConversionKind.ImplicitNumeric:
case ConversionKind.ImplicitNullable:
case ConversionKind.ImplicitReference:
case ConversionKind.Boxing:
case ConversionKind.ImplicitConstant:
case ConversionKind.ImplicitPointer:
case ConversionKind.ImplicitPointerToVoid:
case ConversionKind.ImplicitTuple:
case ConversionKind.ImplicitSpan:
return true;
default:
return false;
}
}
private Conversion ClassifyStandardImplicitConversion(BoundExpression sourceExpression, TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert(sourceExpression is null || Compilation is not null);
Debug.Assert(sourceExpression != null || (object)source != null);
Debug.Assert(sourceExpression == null || (object)sourceExpression.Type == (object)source);
Debug.Assert((object)destination != null);
// SPEC: The following implicit conversions are classified as standard implicit conversions:
// SPEC: Identity conversions
// SPEC: Implicit numeric conversions
// SPEC: Implicit nullable conversions
// SPEC: Null literal conversions
// SPEC: Implicit reference conversions
// SPEC: Boxing conversions
// SPEC: Implicit constant expression conversions
// SPEC: Implicit conversions involving type parameters
//
// and in unsafe code:
//
// SPEC: From any pointer type to void*
//
// SPEC ERROR:
// The specification does not say to take into account the conversion from
// the *expression*, only its *type*. But the expression may not have a type
// (because it is null, a method group, or a lambda), or the expression might
// be convertible to the destination type via a constant numeric conversion.
// For example, the native compiler allows "C c = 1;" to work if C is a class which
// has an implicit conversion from byte to C, despite the fact that there is
// obviously no standard implicit conversion from *int* to *byte*.
// Similarly, if a struct S has an implicit conversion from string to S, then
// "S s = null;" should be allowed.
//
// We extend the definition of standard implicit conversions to include
// all of the implicit conversions that are allowed based on an expression,
// with the exception of switch expression, interpolated string builder,
// and collection expression conversions.
Conversion conversion = ClassifyImplicitBuiltInConversionFromExpression(sourceExpression, source, destination, ref useSiteInfo);
if (conversion.Exists &&
!conversion.IsInterpolatedStringHandler &&
!isImplicitCollectionExpressionConversion(conversion))
{
Debug.Assert(isStandardImplicitConversionFromExpression(conversion.Kind));
return conversion;
}
if ((object)source != null)
{
return ClassifyStandardImplicitConversion(source, destination, ref useSiteInfo);
}
return Conversion.NoConversion;
static bool isImplicitCollectionExpressionConversion(Conversion conversion)
{
return conversion switch
{
{ Kind: ConversionKind.CollectionExpression } => true,
{ Kind: ConversionKind.ImplicitNullable, UnderlyingConversions: [{ Kind: ConversionKind.CollectionExpression }] } => true,
_ => false,
};
}
static bool isStandardImplicitConversionFromExpression(ConversionKind kind)
{
if (IsStandardImplicitConversionFromType(kind))
{
return true;
}
switch (kind)
{
case ConversionKind.NullLiteral:
case ConversionKind.AnonymousFunction:
case ConversionKind.MethodGroup:
case ConversionKind.ImplicitEnumeration:
case ConversionKind.ImplicitDynamic:
case ConversionKind.ImplicitNullToPointer:
case ConversionKind.ImplicitTupleLiteral:
case ConversionKind.StackAllocToPointerType:
case ConversionKind.StackAllocToSpanType:
case ConversionKind.InlineArray:
case ConversionKind.InterpolatedString:
return true;
default:
return false;
}
}
}
private Conversion ClassifyStandardImplicitConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
var conversion = classifyConversion(source, destination, ref useSiteInfo);
Debug.Assert(conversion.Kind == ConversionKind.NoConversion || IsStandardImplicitConversionFromType(conversion.Kind));
return conversion;
Conversion classifyConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
if (HasIdentityConversionInternal(source, destination))
{
return Conversion.Identity;
}
if (HasImplicitNumericConversion(source, destination))
{
return Conversion.ImplicitNumeric;
}
var nullableConversion = ClassifyImplicitNullableConversion(source, destination, ref useSiteInfo);
if (nullableConversion.Exists)
{
return nullableConversion;
}
if (source is FunctionTypeSymbol)
{
Debug.Assert(false);
return Conversion.NoConversion;
}
if (HasImplicitReferenceConversion(source, destination, ref useSiteInfo))
{
return Conversion.ImplicitReference;
}
if (HasBoxingConversion(source, destination, ref useSiteInfo))
{
return Conversion.Boxing;
}
if (HasImplicitPointerToVoidConversion(source, destination))
{
return Conversion.PointerToVoid;
}
if (HasImplicitPointerConversion(source, destination, ref useSiteInfo))
{
return Conversion.ImplicitPointer;
}
var tupleConversion = ClassifyImplicitTupleConversion(source, destination, ref useSiteInfo);
if (tupleConversion.Exists)
{
return tupleConversion;
}
if (HasImplicitSpanConversion(source, destination, ref useSiteInfo))
{
return Conversion.ImplicitSpan;
}
return Conversion.NoConversion;
}
}
private Conversion ClassifyImplicitBuiltInConversionSlow(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
if (source.IsVoidType() || destination.IsVoidType())
{
return Conversion.NoConversion;
}
Conversion conversion = ClassifyStandardImplicitConversion(source, destination, ref useSiteInfo);
if (conversion.Exists)
{
return conversion;
}
return Conversion.NoConversion;
}
private Conversion GetImplicitUserDefinedConversion(BoundExpression sourceExpression, TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
var conversionResult = AnalyzeImplicitUserDefinedConversions(sourceExpression, source, destination, ref useSiteInfo);
return new Conversion(conversionResult, isImplicit: true);
}
private Conversion GetImplicitUserDefinedConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
return GetImplicitUserDefinedConversion(sourceExpression: null, source, destination, ref useSiteInfo);
}
private Conversion ClassifyExplicitBuiltInOnlyConversion(TypeSymbol source, TypeSymbol destination, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, bool forCast)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
if (source.IsVoidType() || destination.IsVoidType())
{
return Conversion.NoConversion;
}
// The call to HasExplicitNumericConversion isn't necessary, because it is always tested
// already by the "FastConversion" code.
Debug.Assert(!HasExplicitNumericConversion(source, destination));
//if (HasExplicitNumericConversion(source, specialTypeSource, destination, specialTypeDest))
//{
// return Conversion.ExplicitNumeric;
//}
if (HasSpecialIntPtrConversion(source, destination))
{
return Conversion.IntPtr;
}
if (HasExplicitEnumerationConversion(source, destination))
{
return Conversion.ExplicitEnumeration;
}
var nullableConversion = ClassifyExplicitNullableConversion(source, destination, isChecked: isChecked, ref useSiteInfo, forCast);
if (nullableConversion.Exists)
{
return nullableConversion;
}
if (HasExplicitReferenceConversion(source, destination, ref useSiteInfo))
{
return (source.Kind == SymbolKind.DynamicType) ? Conversion.ExplicitDynamic : Conversion.ExplicitReference;
}
if (HasUnboxingConversion(source, destination, ref useSiteInfo))
{
return Conversion.Unboxing;
}
var tupleConversion = ClassifyExplicitTupleConversion(source, destination, isChecked: isChecked, ref useSiteInfo, forCast);
if (tupleConversion.Exists)
{
return tupleConversion;
}
if (HasPointerToPointerConversion(source, destination))
{
return Conversion.PointerToPointer;
}
if (HasPointerToIntegerConversion(source, destination))
{
return Conversion.PointerToInteger;
}
if (HasIntegerToPointerConversion(source, destination))
{
return Conversion.IntegerToPointer;
}
if (HasExplicitDynamicConversion(source, destination))
{
return Conversion.ExplicitDynamic;
}
if (HasExplicitSpanConversion(source, destination, ref useSiteInfo))
{
return Conversion.ExplicitSpan;
}
return Conversion.NoConversion;
}
private Conversion GetExplicitUserDefinedConversion(BoundExpression sourceExpression, TypeSymbol source, TypeSymbol destination, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
UserDefinedConversionResult conversionResult = AnalyzeExplicitUserDefinedConversions(sourceExpression, source, destination, isChecked: isChecked, ref useSiteInfo);
return new Conversion(conversionResult, isImplicit: false);
}
private Conversion GetExplicitUserDefinedConversion(TypeSymbol source, TypeSymbol destination, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
return GetExplicitUserDefinedConversion(sourceExpression: null, source, destination, isChecked, ref useSiteInfo);
}
private Conversion DeriveStandardExplicitFromOppositeStandardImplicitConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
var oppositeConversion = ClassifyStandardImplicitConversion(destination, source, ref useSiteInfo);
Conversion impliedExplicitConversion;
switch (oppositeConversion.Kind)
{
case ConversionKind.Identity:
impliedExplicitConversion = Conversion.Identity;
break;
case ConversionKind.ImplicitNumeric:
impliedExplicitConversion = Conversion.ExplicitNumeric;
break;
case ConversionKind.ImplicitReference:
impliedExplicitConversion = Conversion.ExplicitReference;
break;
case ConversionKind.Boxing:
impliedExplicitConversion = Conversion.Unboxing;
break;
case ConversionKind.NoConversion:
impliedExplicitConversion = Conversion.NoConversion;
break;
case ConversionKind.ImplicitPointerToVoid:
impliedExplicitConversion = Conversion.PointerToPointer;
break;
case ConversionKind.ImplicitTuple:
// only implicit tuple conversions are standard conversions,
// having implicit conversion in the other direction does not help here.
impliedExplicitConversion = Conversion.NoConversion;
break;
case ConversionKind.ImplicitNullable:
var strippedSource = source.StrippedType();
var strippedDestination = destination.StrippedType();
var underlyingConversion = DeriveStandardExplicitFromOppositeStandardImplicitConversion(strippedSource, strippedDestination, ref useSiteInfo);
// the opposite underlying conversion may not exist
// for example if underlying conversion is implicit tuple
impliedExplicitConversion = underlyingConversion.Exists ?
Conversion.MakeNullableConversion(ConversionKind.ExplicitNullable, underlyingConversion) :
Conversion.NoConversion;
break;
case ConversionKind.ImplicitSpan:
impliedExplicitConversion = Conversion.NoConversion;
break;
default:
throw ExceptionUtilities.UnexpectedValue(oppositeConversion.Kind);
}
return impliedExplicitConversion;
}
#nullable enable
/// <summary>
/// IsBaseInterface returns true if baseType is on the base interface list of derivedType or
/// any base class of derivedType. It may be on the base interface list either directly or
/// indirectly.
/// * baseType must be an interface.
/// * type parameters do not have base interfaces. (They have an "effective interface list".)
/// * an interface is not a base of itself.
/// * this does not check for variance conversions; if a type inherits from
/// IEnumerable<string> then IEnumerable<object> is not a base interface.
/// </summary>
public bool IsBaseInterface(TypeSymbol baseType, TypeSymbol derivedType, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)baseType != null);
Debug.Assert((object)derivedType != null);
if (!baseType.IsInterfaceType())
{
return false;
}
var d = derivedType as NamedTypeSymbol;
if (d is null)
{
return false;
}
foreach (var iface in d.AllInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteInfo))
{
if (HasIdentityConversionInternal(iface, baseType))
{
return true;
}
}
return false;
}
// IsBaseClass returns true if and only if baseType is a base class of derivedType, period.
//
// * interfaces do not have base classes. (Structs, enums and classes other than object do.)
// * a class is not a base class of itself
// * type parameters do not have base classes. (They have "effective base classes".)
// * all base classes must be classes
// * dynamics are removed; if we have class D : B<dynamic> then B<object> is a
// base class of D. However, dynamic is never a base class of anything.
public bool IsBaseClass(TypeSymbol derivedType, TypeSymbol baseType, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)derivedType != null);
Debug.Assert((object)baseType != null);
// A base class has got to be a class. The derived type might be a struct, enum, or delegate.
if (!baseType.IsClassType())
{
return false;
}
for (TypeSymbol b = derivedType.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteInfo); (object)b != null; b = b.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteInfo))
{
if (HasIdentityConversionInternal(b, baseType))
{
return true;
}
}
return false;
}
/// <summary>
/// returns true when implicit conversion is not necessarily the same as explicit conversion
/// </summary>
private static bool ExplicitConversionMayDifferFromImplicit(Conversion implicitConversion)
{
switch (implicitConversion.Kind)
{
case ConversionKind.ImplicitUserDefined:
case ConversionKind.ImplicitDynamic:
case ConversionKind.ImplicitTuple:
case ConversionKind.ImplicitTupleLiteral:
case ConversionKind.ImplicitNullable:
case ConversionKind.ConditionalExpression:
case ConversionKind.ImplicitSpan:
return true;
default:
return false;
}
}
#nullable disable
private Conversion ClassifyImplicitBuiltInConversionFromExpression(BoundExpression sourceExpression, TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert(sourceExpression is null || Compilation is not null);
Debug.Assert(sourceExpression != null || (object)source != null);
Debug.Assert(sourceExpression == null || (object)sourceExpression.Type == (object)source);
Debug.Assert((object)destination != null);
if (HasImplicitDynamicConversionFromExpression(source, destination))
{
return Conversion.ImplicitDynamic;
}
// The following conversions only exist for certain form of expressions,
// if we have no expression none if them is applicable.
if (sourceExpression == null)
{
return Conversion.NoConversion;
}
if (HasImplicitEnumerationConversion(sourceExpression, destination))
{
return Conversion.ImplicitEnumeration;
}
var constantConversion = ClassifyImplicitConstantExpressionConversion(sourceExpression, destination);
if (constantConversion.Exists)
{
return constantConversion;
}
switch (sourceExpression.Kind)
{
case BoundKind.Literal:
var nullLiteralConversion = ClassifyNullLiteralConversion(sourceExpression, destination);
if (nullLiteralConversion.Exists)
{
return nullLiteralConversion;
}
break;
case BoundKind.DefaultLiteral:
return Conversion.DefaultLiteral;
case BoundKind.ExpressionWithNullability:
{
var innerExpression = ((BoundExpressionWithNullability)sourceExpression).Expression;
var innerConversion = ClassifyImplicitBuiltInConversionFromExpression(innerExpression, innerExpression.Type, destination, ref useSiteInfo);
if (innerConversion.Exists)
{
return innerConversion;
}
break;
}
case BoundKind.TupleLiteral:
var tupleConversion = ClassifyImplicitTupleLiteralConversion((BoundTupleLiteral)sourceExpression, destination, ref useSiteInfo);
if (tupleConversion.Exists)
{
return tupleConversion;
}
break;
case BoundKind.UnboundLambda:
if (HasAnonymousFunctionConversion(sourceExpression, destination, this.Compilation))
{
return Conversion.AnonymousFunction;
}
break;
case BoundKind.MethodGroup:
Conversion methodGroupConversion = GetMethodGroupDelegateConversion((BoundMethodGroup)sourceExpression, destination, ref useSiteInfo);
if (methodGroupConversion.Exists)
{
return methodGroupConversion;
}
break;
case BoundKind.UnconvertedInterpolatedString:
case BoundKind.BinaryOperator when ((BoundBinaryOperator)sourceExpression).IsUnconvertedInterpolatedStringAddition:
Conversion interpolatedStringConversion = GetInterpolatedStringConversion(sourceExpression, destination, ref useSiteInfo);
if (interpolatedStringConversion.Exists)
{
return interpolatedStringConversion;
}
break;
case BoundKind.StackAllocArrayCreation:
var stackAllocConversion = GetStackAllocConversion((BoundStackAllocArrayCreation)sourceExpression, destination, ref useSiteInfo);
if (stackAllocConversion.Exists)
{
return stackAllocConversion;
}
break;
case BoundKind.UnconvertedAddressOfOperator when destination is FunctionPointerTypeSymbol funcPtrType:
var addressOfConversion = GetMethodGroupFunctionPointerConversion(((BoundUnconvertedAddressOfOperator)sourceExpression).Operand, funcPtrType, ref useSiteInfo);
if (addressOfConversion.Exists)
{
return addressOfConversion;
}
break;
case BoundKind.ThrowExpression:
return Conversion.ImplicitThrow;
case BoundKind.UnconvertedObjectCreationExpression:
return Conversion.ObjectCreation;
case BoundKind.UnconvertedCollectionExpression:
var collectionExpressionConversion = GetImplicitCollectionExpressionConversion((BoundUnconvertedCollectionExpression)sourceExpression, destination, ref useSiteInfo);
if (collectionExpressionConversion.Exists)
{
return collectionExpressionConversion;
}
break;
}
// Neither Span<T>, nor ReadOnlySpan<T> can be wrapped into a Nullable<T>, therefore, there is no point to check for an attempt to convert to Nullable types here.
if (!IsAttributeArgumentBinding && !IsParameterDefaultValueBinding && // These checks prevent cycles caused by attribute binding when HasInlineArrayAttribute check triggers that.
source?.HasInlineArrayAttribute(out _) == true &&
source.TryGetInlineArrayElementField() is { TypeWithAnnotations: var elementType } &&
(destination.OriginalDefinition.Equals(Compilation.GetWellKnownType(WellKnownType.System_Span_T), TypeCompareKind.AllIgnoreOptions) ||
destination.OriginalDefinition.Equals(Compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions)) &&
HasIdentityConversionInternal(((NamedTypeSymbol)destination.OriginalDefinition).Construct(ImmutableArray.Create(elementType)), destination))
{
return Conversion.InlineArray;
}
return Conversion.NoConversion;
}
#nullable enable
private Conversion GetImplicitCollectionExpressionConversion(BoundUnconvertedCollectionExpression collectionExpression, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
var collectionExpressionConversion = GetCollectionExpressionConversion(collectionExpression, destination, ref useSiteInfo);
if (collectionExpressionConversion.Exists)
{
return collectionExpressionConversion;
}
// strip nullable from the destination
//
// the following should work and it is an ImplicitNullable conversion
// ImmutableArray<int>? x = [1, 2];
if (destination.IsNullableType(out var underlyingDestination))
{
var underlyingConversion = GetCollectionExpressionConversion(collectionExpression, underlyingDestination, ref useSiteInfo);
if (underlyingConversion.Exists)
{
return new Conversion(ConversionKind.ImplicitNullable, ImmutableArray.Create(underlyingConversion));
}
}
return Conversion.NoConversion;
}
#nullable disable
private Conversion GetSwitchExpressionConversion(BoundExpression source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert(Compilation is not null);
switch (source)
{
case BoundConvertedSwitchExpression _:
// It has already been subjected to a switch expression conversion.
return Conversion.NoConversion;
case BoundUnconvertedSwitchExpression switchExpression:
var innerConversions = ArrayBuilder<Conversion>.GetInstance(switchExpression.SwitchArms.Length);
foreach (var arm in switchExpression.SwitchArms)
{
var nestedConversion = this.ClassifyImplicitConversionFromExpression(arm.Value, destination, ref useSiteInfo);
if (!nestedConversion.Exists)
{
innerConversions.Free();
return Conversion.NoConversion;
}
innerConversions.Add(nestedConversion);
}
return Conversion.MakeSwitchExpression(innerConversions.ToImmutableAndFree());
default:
return Conversion.NoConversion;
}
}
private Conversion GetConditionalExpressionConversion(BoundExpression source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert(Compilation is not null);
if (!(source is BoundUnconvertedConditionalOperator conditionalOperator))
return Conversion.NoConversion;
var trueConversion = this.ClassifyImplicitConversionFromExpression(conditionalOperator.Consequence, destination, ref useSiteInfo);
if (!trueConversion.Exists)
return Conversion.NoConversion;
var falseConversion = this.ClassifyImplicitConversionFromExpression(conditionalOperator.Alternative, destination, ref useSiteInfo);
if (!falseConversion.Exists)
return Conversion.NoConversion;
return Conversion.MakeConditionalExpression(ImmutableArray.Create(trueConversion, falseConversion));
}
private static Conversion ClassifyNullLiteralConversion(BoundExpression source, TypeSymbol destination)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
if (!source.IsLiteralNull())
{
return Conversion.NoConversion;
}
// SPEC: An implicit conversion exists from the null literal to any nullable type.
if (destination.IsNullableType())
{
// The spec defines a "null literal conversion" specifically as a conversion from
// null to nullable type.
return Conversion.NullLiteral;
}
// SPEC: An implicit conversion exists from the null literal to any reference type.
// SPEC: An implicit conversion exists from the null literal to type parameter T,
// SPEC: provided T is known to be a reference type. [...] The conversion [is] classified
// SPEC: as implicit reference conversion.
if (destination.IsReferenceType)
{
return Conversion.ImplicitReference;
}
// SPEC: The set of implicit conversions is extended to include...
// SPEC: ... from the null literal to any pointer type.
if (destination.IsPointerOrFunctionPointer())
{
return Conversion.NullToPointer;
}
return Conversion.NoConversion;
}
private static Conversion ClassifyImplicitConstantExpressionConversion(BoundExpression source, TypeSymbol destination)
{
if (HasImplicitConstantExpressionConversion(source, destination))
{
return Conversion.ImplicitConstant;
}
// strip nullable from the destination
//
// the following should work and it is an ImplicitNullable conversion
// int? x = 1;
if (destination.Kind == SymbolKind.NamedType)
{
if (destination.IsNullableType(out var underlyingDestination) &&
HasImplicitConstantExpressionConversion(source, underlyingDestination))
{
return Conversion.ImplicitNullableWithImplicitConstantUnderlying;
}
}
return Conversion.NoConversion;
}
private Conversion ClassifyImplicitTupleLiteralConversion(BoundTupleLiteral source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert(Compilation is not null);
var tupleConversion = GetImplicitTupleLiteralConversion(source, destination, ref useSiteInfo);
if (tupleConversion.Exists)
{
return tupleConversion;
}
// strip nullable from the destination
//
// the following should work and it is an ImplicitNullable conversion
// (int, double)? x = (1,2);
if (destination.IsNullableType(out var underlyingDestination))
{
var underlyingTupleConversion = GetImplicitTupleLiteralConversion(source, underlyingDestination, ref useSiteInfo);
if (underlyingTupleConversion.Exists)
{
return new Conversion(ConversionKind.ImplicitNullable, ImmutableArray.Create(underlyingTupleConversion));
}
}
return Conversion.NoConversion;
}
private Conversion ClassifyExplicitTupleLiteralConversion(BoundTupleLiteral source, TypeSymbol destination, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, bool forCast)
{
Debug.Assert(Compilation is not null);
var tupleConversion = GetExplicitTupleLiteralConversion(source, destination, isChecked: isChecked, ref useSiteInfo, forCast);
if (tupleConversion.Exists)
{
return tupleConversion;
}
// strip nullable from the destination
//
// the following should work and it is an ExplicitNullable conversion
// var x = ((byte, string)?)(1,null);
if (destination.Kind == SymbolKind.NamedType)
{
if (destination.IsNullableType(out var underlyingDestination))
{
var underlyingTupleConversion = GetExplicitTupleLiteralConversion(source, underlyingDestination, isChecked: isChecked, ref useSiteInfo, forCast);
if (underlyingTupleConversion.Exists)
{
return new Conversion(ConversionKind.ExplicitNullable, ImmutableArray.Create(underlyingTupleConversion));
}
}
}
return Conversion.NoConversion;
}
internal static bool HasImplicitConstantExpressionConversion(BoundExpression source, TypeSymbol destination)
{
var constantValue = source.ConstantValueOpt;
if (constantValue == null || (object)source.Type == null)
{
return false;
}
// An implicit constant expression conversion permits the following conversions:
// A constant-expression of type int can be converted to type sbyte, byte, short,
// ushort, uint, or ulong, provided the value of the constant-expression is within the
// range of the destination type.
var specialSource = source.Type.GetSpecialTypeSafe();
if (specialSource == SpecialType.System_Int32)
{
//if the constant value could not be computed, be generous and assume the conversion will work
int value = constantValue.IsBad ? 0 : constantValue.Int32Value;
switch (destination.GetSpecialTypeSafe())
{
case SpecialType.System_Byte:
return byte.MinValue <= value && value <= byte.MaxValue;
case SpecialType.System_SByte:
return sbyte.MinValue <= value && value <= sbyte.MaxValue;
case SpecialType.System_Int16:
return short.MinValue <= value && value <= short.MaxValue;
case SpecialType.System_IntPtr when destination.IsNativeIntegerType:
return true;
case SpecialType.System_UInt32:
case SpecialType.System_UIntPtr when destination.IsNativeIntegerType:
return uint.MinValue <= value;
case SpecialType.System_UInt64:
return (int)ulong.MinValue <= value;
case SpecialType.System_UInt16:
return ushort.MinValue <= value && value <= ushort.MaxValue;
default:
return false;
}
}
else if (specialSource == SpecialType.System_Int64 && destination.GetSpecialTypeSafe() == SpecialType.System_UInt64 && (constantValue.IsBad || 0 <= constantValue.Int64Value))
{
// A constant-expression of type long can be converted to type ulong, provided the
// value of the constant-expression is not negative.
return true;
}
return false;
}
#nullable enable
private Conversion ClassifyExplicitOnlyConversionFromExpression(BoundExpression sourceExpression, TypeSymbol destination, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, bool forCast)
{
Debug.Assert(sourceExpression != null);
Debug.Assert(Compilation != null);
Debug.Assert((object)destination != null);
// NB: need to check for explicit tuple literal conversion before checking for explicit conversion from type
// The same literal may have both explicit tuple conversion and explicit tuple literal conversion to the target type.
// They are, however, observably different conversions via the order of argument evaluations and element-wise conversions
if (sourceExpression.Kind == BoundKind.TupleLiteral)
{
Conversion tupleConversion = ClassifyExplicitTupleLiteralConversion((BoundTupleLiteral)sourceExpression, destination, isChecked: isChecked, ref useSiteInfo, forCast);
if (tupleConversion.Exists)
{
return tupleConversion;
}
}
var sourceType = sourceExpression.Type;
if (sourceType is { })
{
// Try using the short-circuit "fast-conversion" path.
Conversion fastConversion = FastClassifyConversion(sourceType, destination);
if (fastConversion.Exists)
{
return fastConversion;
}
else
{
var conversion = ClassifyExplicitBuiltInOnlyConversion(sourceType, destination, isChecked: isChecked, ref useSiteInfo, forCast);
if (conversion.Exists)
{
return conversion;
}
}
}
return GetExplicitUserDefinedConversion(sourceExpression, sourceType, destination, isChecked: isChecked, ref useSiteInfo);
}
private static bool HasImplicitEnumerationConversion(BoundExpression source, TypeSymbol destination)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
// SPEC: An implicit enumeration conversion permits the decimal-integer-literal 0 to be converted to any enum-type
// SPEC: and to any nullable-type whose underlying type is an enum-type.
//
// For historical reasons we actually allow a conversion from any *numeric constant
// zero* to be converted to any enum type, not just the literal integer zero.
bool validType = destination.IsEnumType() ||
destination.IsNullableType() && destination.GetNullableUnderlyingType().IsEnumType();
if (!validType)
{
return false;
}
var sourceConstantValue = source.ConstantValueOpt;
return sourceConstantValue != null &&
source.Type is object &&
IsNumericType(source.Type) &&
IsConstantNumericZero(sourceConstantValue);
}
private static LambdaConversionResult IsAnonymousFunctionCompatibleWithDelegate(UnboundLambda anonymousFunction, TypeSymbol type, CSharpCompilation compilation, bool isTargetExpressionTree)
{
Debug.Assert((object)anonymousFunction != null);
Debug.Assert((object)type != null);
// SPEC: An anonymous-method-expression or lambda-expression is classified as an anonymous function.
// SPEC: The expression does not have a type but can be implicitly converted to a compatible delegate
// SPEC: type or expression tree type. Specifically, a delegate type D is compatible with an
// SPEC: anonymous function F provided:
var delegateType = (NamedTypeSymbol)type;
var invokeMethod = delegateType.DelegateInvokeMethod;
if (invokeMethod is null || invokeMethod.HasUseSiteError)
{
return LambdaConversionResult.BadTargetType;
}
if (anonymousFunction.HasExplicitReturnType(out var refKind, out var returnType))
{
if (invokeMethod.RefKind != refKind ||
!invokeMethod.ReturnType.Equals(returnType.Type, TypeCompareKind.AllIgnoreOptions))
{
return LambdaConversionResult.MismatchedReturnType;
}
}
var delegateParameters = invokeMethod.Parameters;
// SPEC: If F contains an anonymous-function-signature, then D and F have the same number of parameters.
// SPEC: If F does not contain an anonymous-function-signature, then D may have zero or more parameters
// SPEC: of any type, as long as no parameter of D has the out parameter modifier.
if (anonymousFunction.HasSignature)
{
if (anonymousFunction.ParameterCount != invokeMethod.ParameterCount)
{
return LambdaConversionResult.BadParameterCount;
}
// SPEC: If F has an explicitly typed parameter list, each parameter in D has the same type
// SPEC: and modifiers as the corresponding parameter in F.
// SPEC: If F has an implicitly typed parameter list, D has no ref or out parameters.
if (anonymousFunction.HasExplicitlyTypedParameterList)
{
for (int p = 0; p < delegateParameters.Length; ++p)
{
if (!OverloadResolution.AreRefsCompatibleForMethodConversion(
candidateMethodParameterRefKind: anonymousFunction.RefKind(p),
delegateParameterRefKind: delegateParameters[p].RefKind,
compilation) ||
!delegateParameters[p].Type.Equals(anonymousFunction.ParameterType(p), TypeCompareKind.AllIgnoreOptions))
{
return LambdaConversionResult.MismatchedParameterType;
}
}
}
else
{
for (int p = 0; p < delegateParameters.Length; ++p)
{
if (delegateParameters[p].RefKind != RefKind.None)
{
return LambdaConversionResult.RefInImplicitlyTypedLambda;
}
}
// In C# it is not possible to make a delegate type
// such that one of its parameter types is a static type. But static types are
// in metadata just sealed abstract types; there is nothing stopping someone in
// another language from creating a delegate with a static type for a parameter,
// though the only argument you could pass for that parameter is null.
//
// In the native compiler we forbid conversion of an anonymous function that has
// an implicitly-typed parameter list to a delegate type that has a static type
// for a formal parameter type. However, we do *not* forbid it for an explicitly-
// typed lambda (because we already require that the explicitly typed parameter not
// be static) and we do not forbid it for an anonymous method with the entire
// parameter list missing (because the body cannot possibly have a parameter that
// is of static type, even though this means that we will be generating a hidden
// method with a parameter of static type.)
//
// We also allow more exotic situations to work in the native compiler. For example,
// though it is not possible to convert x=>{} to Action<GC>, it is possible to convert
// it to Action<List<GC>> should there be a language that allows you to construct
// a variable of that type.
//
// We might consider beefing up this rule to disallow a conversion of *any* anonymous
// function to *any* delegate that has a static type *anywhere* in the parameter list.
for (int p = 0; p < delegateParameters.Length; ++p)
{
if (delegateParameters[p].TypeWithAnnotations.IsStatic)
{
return LambdaConversionResult.StaticTypeInImplicitlyTypedLambda;
}
}
}
}
else
{
for (int p = 0; p < delegateParameters.Length; ++p)
{
if (delegateParameters[p].RefKind == RefKind.Out)
{
return LambdaConversionResult.MissingSignatureWithOutParameter;
}
}
}
// Ensure the body can be converted to that delegate type
var bound = anonymousFunction.Bind(delegateType, isTargetExpressionTree);
if (ErrorFacts.PreventsSuccessfulDelegateConversion(bound.Diagnostics.Diagnostics))
{
return LambdaConversionResult.BindingFailed;
}
return LambdaConversionResult.Success;
}
private static LambdaConversionResult IsAnonymousFunctionCompatibleWithExpressionTree(UnboundLambda anonymousFunction, NamedTypeSymbol type, CSharpCompilation compilation)
{
Debug.Assert((object)anonymousFunction != null);
Debug.Assert((object)type != null);
Debug.Assert(type.IsExpressionTree());
// SPEC OMISSION:
//
// The C# 3 spec said that anonymous methods and statement lambdas are *convertible* to expression tree
// types if the anonymous method/statement lambda is convertible to its delegate type; however, actually
// *using* such a conversion is an error. However, that is not what we implemented. In C# 3 we implemented
// that an anonymous method is *not convertible* to an expression tree type, period. (Statement lambdas
// used the rule described in the spec.)
//
// This appears to be a spec omission; the intention is to make old-style anonymous methods not
// convertible to expression trees.
var delegateType = type.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type;
if (!delegateType.IsDelegateType())
{
return LambdaConversionResult.ExpressionTreeMustHaveDelegateTypeArgument;
}
if (anonymousFunction.Syntax.Kind() == SyntaxKind.AnonymousMethodExpression)
{
return LambdaConversionResult.ExpressionTreeFromAnonymousMethod;
}
return IsAnonymousFunctionCompatibleWithDelegate(anonymousFunction, delegateType, compilation, isTargetExpressionTree: true);
}
internal bool IsAssignableFromMulticastDelegate(TypeSymbol type, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
var multicastDelegateType = corLibrary.GetSpecialType(SpecialType.System_MulticastDelegate);
multicastDelegateType.AddUseSiteInfo(ref useSiteInfo);
return ClassifyImplicitConversionFromType(multicastDelegateType, type, ref useSiteInfo).Exists;
}
public static LambdaConversionResult IsAnonymousFunctionCompatibleWithType(UnboundLambda anonymousFunction, TypeSymbol type, CSharpCompilation compilation)
{
Debug.Assert((object)anonymousFunction != null);
Debug.Assert((object)type != null);
if (type.IsDelegateType())
{
return IsAnonymousFunctionCompatibleWithDelegate(anonymousFunction, type, compilation, isTargetExpressionTree: false);
}
else if (type.IsExpressionTree())
{
return IsAnonymousFunctionCompatibleWithExpressionTree(anonymousFunction, (NamedTypeSymbol)type, compilation);
}
return LambdaConversionResult.BadTargetType;
}
private static bool HasAnonymousFunctionConversion(BoundExpression source, TypeSymbol destination, CSharpCompilation compilation)
{
Debug.Assert(source != null);
Debug.Assert((object)destination != null);
if (source.Kind != BoundKind.UnboundLambda)
{
return false;
}
return IsAnonymousFunctionCompatibleWithType((UnboundLambda)source, destination, compilation) == LambdaConversionResult.Success;
}
internal static CollectionExpressionTypeKind GetCollectionExpressionTypeKind(CSharpCompilation compilation, TypeSymbol destination, out TypeWithAnnotations elementType)
{
Debug.Assert(compilation is { });
if (destination is ArrayTypeSymbol arrayType)
{
if (arrayType.IsSZArray)
{
elementType = arrayType.ElementTypeWithAnnotations;
return CollectionExpressionTypeKind.Array;
}
}
else if (IsSpanOrListType(compilation, destination, WellKnownType.System_Span_T, out elementType))
{
return CollectionExpressionTypeKind.Span;
}
else if (IsSpanOrListType(compilation, destination, WellKnownType.System_ReadOnlySpan_T, out elementType))
{
return CollectionExpressionTypeKind.ReadOnlySpan;
}
else if ((destination as NamedTypeSymbol)?.HasCollectionBuilderAttribute(out _, out _) == true)
{
elementType = default;
return CollectionExpressionTypeKind.CollectionBuilder;
}
else if (implementsSpecialInterface(compilation, destination, SpecialType.System_Collections_IEnumerable))
{
// ^ This implementation differs from Binder.CollectionInitializerTypeImplementsIEnumerable().
// That method checks for an implicit conversion from IEnumerable to the collection type, to
// match earlier implementation, even though it states that walking the implemented interfaces
// would be better. If we use CollectionInitializerTypeImplementsIEnumerable() here, we'd need
// to check for nullable to disallow: Nullable<StructCollection> s = [];
// Instead, we just walk the implemented interfaces.
elementType = default;
return CollectionExpressionTypeKind.ImplementsIEnumerable;
}
else if (destination.IsArrayInterface(out elementType))
{
return CollectionExpressionTypeKind.ArrayInterface;
}
elementType = default;
return CollectionExpressionTypeKind.None;
static bool implementsSpecialInterface(CSharpCompilation compilation, TypeSymbol targetType, SpecialType specialInterface)
{
var allInterfaces = targetType.GetAllInterfacesOrEffectiveInterfaces();
var specialType = compilation.GetSpecialType(specialInterface);
return allInterfaces.Any(static (a, b) => ReferenceEquals(a.OriginalDefinition, b), specialType);
}
}
internal static bool IsSpanOrListType(CSharpCompilation compilation, TypeSymbol targetType, WellKnownType spanType, [NotNullWhen(true)] out TypeWithAnnotations elementType)
{
if (targetType is NamedTypeSymbol { Arity: 1 } namedType
&& ReferenceEquals(namedType.OriginalDefinition, compilation.GetWellKnownType(spanType)))
{
elementType = namedType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0];
return true;
}
elementType = default;
return false;
}
#nullable disable
internal Conversion ClassifyImplicitUserDefinedConversionForV6SwitchGoverningType(TypeSymbol sourceType, out TypeSymbol switchGoverningType, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
// SPEC: The governing type of a switch statement is established by the switch expression.
// SPEC: 1) If the type of the switch expression is sbyte, byte, short, ushort, int, uint,
// SPEC: long, ulong, bool, char, string, or an enum-type, or if it is the nullable type
// SPEC: corresponding to one of these types, then that is the governing type of the switch statement.
// SPEC: 2) Otherwise, exactly one user-defined implicit conversion (§6.4) must exist from the
// SPEC: type of the switch expression to one of the following possible governing types:
// SPEC: sbyte, byte, short, ushort, int, uint, long, ulong, char, string, or, a nullable type
// SPEC: corresponding to one of those types
// NOTE: We should be called only if (1) is false for source type.
Debug.Assert((object)sourceType != null);
Debug.Assert(!sourceType.IsValidV6SwitchGoverningType());
UserDefinedConversionResult result = AnalyzeImplicitUserDefinedConversionForV6SwitchGoverningType(sourceType, ref useSiteInfo);
if (result.Kind == UserDefinedConversionResultKind.Valid)
{
UserDefinedConversionAnalysis analysis = result.Results[result.Best];
switchGoverningType = analysis.ToType;
Debug.Assert(switchGoverningType.IsValidV6SwitchGoverningType(isTargetTypeOfUserDefinedOp: true));
}
else
{
switchGoverningType = null;
}
return new Conversion(result, isImplicit: true);
}
internal Conversion GetCallerLineNumberConversion(TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
var greenNode = new Syntax.InternalSyntax.LiteralExpressionSyntax(SyntaxKind.NumericLiteralExpression, new Syntax.InternalSyntax.SyntaxToken(SyntaxKind.NumericLiteralToken));
var syntaxNode = new LiteralExpressionSyntax(greenNode, null, 0);
TypeSymbol expectedAttributeType = corLibrary.GetSpecialType(SpecialType.System_Int32);
BoundLiteral intMaxValueLiteral = new BoundLiteral(syntaxNode, ConstantValue.Create(int.MaxValue), expectedAttributeType);
// Below is a duplication of relevant parts of ClassifyStandardImplicitConversion method.
// It needs a compilation instance, but we don't have it and the relevant parts actually do not depend on
// a compilation.
if (HasImplicitEnumerationConversion(intMaxValueLiteral, destination))
{
return Conversion.ImplicitEnumeration;
}
var constantConversion = ClassifyImplicitConstantExpressionConversion(intMaxValueLiteral, destination);
if (constantConversion.Exists)
{
return constantConversion;
}
return ClassifyStandardImplicitConversion(expectedAttributeType, destination, ref useSiteInfo);
}
internal bool HasCallerLineNumberConversion(TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
return GetCallerLineNumberConversion(destination, ref useSiteInfo).Exists;
}
internal bool HasCallerInfoStringConversion(TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
TypeSymbol expectedAttributeType = corLibrary.GetSpecialType(SpecialType.System_String);
Conversion conversion = ClassifyStandardImplicitConversion(expectedAttributeType, destination, ref useSiteInfo);
return conversion.Exists;
}
public static bool HasIdentityConversion(TypeSymbol type1, TypeSymbol type2)
{
return HasIdentityConversionInternal(type1, type2, includeNullability: false);
}
private static bool HasIdentityConversionInternal(TypeSymbol type1, TypeSymbol type2, bool includeNullability)
{
// Spec (6.1.1):
// An identity conversion converts from any type to the same type. This conversion exists
// such that an entity that already has a required type can be said to be convertible to
// that type.
//
// Because object and dynamic are considered equivalent there is an identity conversion
// between object and dynamic, and between constructed types that are the same when replacing
// all occurrences of dynamic with object.
Debug.Assert((object)type1 != null);
Debug.Assert((object)type2 != null);
// Note, when we are paying attention to nullability, we ignore oblivious mismatch.
// See TypeCompareKind.ObliviousNullableModifierMatchesAny
var compareKind = includeNullability ?
TypeCompareKind.AllIgnoreOptions & ~TypeCompareKind.IgnoreNullableModifiersForReferenceTypes :
TypeCompareKind.AllIgnoreOptions;
return type1.Equals(type2, compareKind);
}
private bool HasIdentityConversionInternal(TypeSymbol type1, TypeSymbol type2)
{
return HasIdentityConversionInternal(type1, type2, IncludeNullability);
}
/// <summary>
/// Returns true if:
/// - Either type has no nullability information (oblivious).
/// - Both types cannot have different nullability at the same time,
/// including the case of type parameters that by themselves can represent nullable and not nullable reference types.
/// </summary>
internal bool HasTopLevelNullabilityIdentityConversion(TypeWithAnnotations source, TypeWithAnnotations destination)
{
if (!IncludeNullability)
{
return true;
}
if (source.NullableAnnotation.IsOblivious() || destination.NullableAnnotation.IsOblivious())
{
return true;
}
var sourceIsPossiblyNullableTypeParameter = IsPossiblyNullableTypeTypeParameter(source);
var destinationIsPossiblyNullableTypeParameter = IsPossiblyNullableTypeTypeParameter(destination);
if (sourceIsPossiblyNullableTypeParameter && !destinationIsPossiblyNullableTypeParameter)
{
return destination.NullableAnnotation.IsAnnotated();
}
if (destinationIsPossiblyNullableTypeParameter && !sourceIsPossiblyNullableTypeParameter)
{
return source.NullableAnnotation.IsAnnotated();
}
return source.NullableAnnotation.IsAnnotated() == destination.NullableAnnotation.IsAnnotated();
}
/// <summary>
/// Returns false if source type can be nullable at the same time when destination type can be not nullable,
/// including the case of type parameters that by themselves can represent nullable and not nullable reference types.
/// When either type has no nullability information (oblivious), this method returns true.
/// </summary>
internal bool HasTopLevelNullabilityImplicitConversion(TypeWithAnnotations source, TypeWithAnnotations destination)
{
if (!IncludeNullability)
{
return true;
}
if (source.NullableAnnotation.IsOblivious() || destination.NullableAnnotation.IsOblivious() || destination.NullableAnnotation.IsAnnotated())
{
return true;
}
if (IsPossiblyNullableTypeTypeParameter(source) && !IsPossiblyNullableTypeTypeParameter(destination))
{
return false;
}
return !source.NullableAnnotation.IsAnnotated();
}
private static bool IsPossiblyNullableTypeTypeParameter(in TypeWithAnnotations typeWithAnnotations)
{
var type = typeWithAnnotations.Type;
return type is object &&
(type.IsPossiblyNullableReferenceTypeTypeParameter() || type.IsNullableTypeOrTypeParameter());
}
/// <summary>
/// Returns false if the source does not have an implicit conversion to the destination
/// because of either incompatible top level or nested nullability.
/// </summary>
public bool HasAnyNullabilityImplicitConversion(TypeWithAnnotations source, TypeWithAnnotations destination)
{
Debug.Assert(IncludeNullability);
var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
return HasTopLevelNullabilityImplicitConversion(source, destination) &&
ClassifyImplicitConversionFromType(source.Type, destination.Type, ref discardedUseSiteInfo).Kind != ConversionKind.NoConversion;
}
private static bool HasIdentityConversionToAny(NamedTypeSymbol type, ArrayBuilder<(NamedTypeSymbol ParticipatingType, TypeParameterSymbol ConstrainedToTypeOpt)> targetTypes)
{
foreach (var targetType in targetTypes)
{
if (HasIdentityConversionInternal(type, targetType.ParticipatingType, includeNullability: false))
{
return true;
}
}
return false;
}
public Conversion ConvertExtensionMethodThisArg(TypeSymbol parameterType, TypeSymbol thisType, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, bool isMethodGroupConversion)
{
Debug.Assert((object)thisType != null);
var conversion = this.ClassifyImplicitExtensionMethodThisArgConversion(sourceExpressionOpt: null, thisType, parameterType, ref useSiteInfo, isMethodGroupConversion);
return IsValidExtensionMethodThisArgConversion(conversion) ? conversion : Conversion.NoConversion;
}
// Spec 7.6.5.2: "An extension method ... is eligible if ... [an] implicit identity, reference,
// boxing, or span conversion exists from expr to the type of the first parameter.
// Span conversion is not considered when overload resolution is performed for a method group conversion."
public Conversion ClassifyImplicitExtensionMethodThisArgConversion(BoundExpression sourceExpressionOpt, TypeSymbol sourceType, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, bool isMethodGroupConversion)
{
Debug.Assert(sourceExpressionOpt is null || Compilation is not null);
Debug.Assert(sourceExpressionOpt == null || (object)sourceExpressionOpt.Type == sourceType);
Debug.Assert((object)destination != null);
if ((object)sourceType != null)
{
if (HasIdentityConversionInternal(sourceType, destination))
{
return Conversion.Identity;
}
if (HasBoxingConversion(sourceType, destination, ref useSiteInfo))
{
return Conversion.Boxing;
}
if (HasImplicitReferenceConversion(sourceType, destination, ref useSiteInfo))
{
return Conversion.ImplicitReference;
}
if (!isMethodGroupConversion && HasImplicitSpanConversion(sourceType, destination, ref useSiteInfo))
{
return Conversion.ImplicitSpan;
}
}
if (sourceExpressionOpt?.Kind == BoundKind.TupleLiteral)
{
// GetTupleLiteralConversion is not used with IncludeNullability currently.
// If that changes, the delegate below will need to consider top-level nullability.
Debug.Assert(!IncludeNullability);
var tupleConversion = GetTupleLiteralConversion(
(BoundTupleLiteral)sourceExpressionOpt,
destination,
ref useSiteInfo,
ConversionKind.ImplicitTupleLiteral,
(ConversionsBase conversions, BoundExpression s, TypeWithAnnotations d, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> u, bool forCast) =>
conversions.ClassifyImplicitExtensionMethodThisArgConversion(s, s.Type, d.Type, ref u, isMethodGroupConversion: false),
isChecked: false,
forCast: false);
if (tupleConversion.Exists)
{
return tupleConversion;
}
}
if ((object)sourceType != null)
{
var tupleConversion = ClassifyTupleConversion(
sourceType,
destination,
ref useSiteInfo,
ConversionKind.ImplicitTuple,
(ConversionsBase conversions, TypeWithAnnotations s, TypeWithAnnotations d, bool _, ref CompoundUseSiteInfo<AssemblySymbol> u, bool _) =>
{
if (!conversions.HasTopLevelNullabilityImplicitConversion(s, d))
{
return Conversion.NoConversion;
}
return conversions.ClassifyImplicitExtensionMethodThisArgConversion(sourceExpressionOpt: null, s.Type, d.Type, ref u, isMethodGroupConversion: false);
},
isChecked: false,
forCast: false);
if (tupleConversion.Exists)
{
return tupleConversion;
}
}
return Conversion.NoConversion;
}
// It should be possible to remove IsValidExtensionMethodThisArgConversion
// since ClassifyImplicitExtensionMethodThisArgConversion should only
// return valid conversions. https://github.com/dotnet/roslyn/issues/19622
// Spec 7.6.5.2: "An extension method ... is eligible if ... [an] implicit identity, reference,
// or boxing conversion exists from expr to the type of the first parameter"
public static bool IsValidExtensionMethodThisArgConversion(Conversion conversion)
{
switch (conversion.Kind)
{
case ConversionKind.Identity:
case ConversionKind.Boxing:
case ConversionKind.ImplicitReference:
case ConversionKind.ImplicitSpan:
return true;
case ConversionKind.ImplicitTuple:
case ConversionKind.ImplicitTupleLiteral:
// check if all element conversions satisfy the requirement
foreach (var elementConversion in conversion.UnderlyingConversions)
{
if (!IsValidExtensionMethodThisArgConversion(elementConversion))
{
return false;
}
}
return true;
default:
// Caller should have not have calculated another conversion.
Debug.Assert(conversion.Kind == ConversionKind.NoConversion);
return false;
}
}
#nullable enable
private static ConversionKind GetNumericConversion(TypeSymbol source, TypeSymbol destination)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
if (!IsNumericType(source) || !IsNumericType(destination))
{
return ConversionKind.UnsetConversionKind;
}
if (source.SpecialType == destination.SpecialType)
{
// Notice that there is no implicit numeric conversion from a type to itself. That's an
// identity conversion.
return ConversionKind.UnsetConversionKind;
}
var conversionKind = ConversionEasyOut.ClassifyConversion(source, destination);
Debug.Assert(conversionKind is ConversionKind.ImplicitNumeric or ConversionKind.ExplicitNumeric);
return conversionKind;
}
private static bool HasImplicitNumericConversion(TypeSymbol source, TypeSymbol destination)
{
return GetNumericConversion(source, destination) == ConversionKind.ImplicitNumeric;
}
private static bool HasExplicitNumericConversion(TypeSymbol source, TypeSymbol destination)
{
// SPEC: The explicit numeric conversions are the conversions from a numeric-type to another
// SPEC: numeric-type for which an implicit numeric conversion does not already exist.
return GetNumericConversion(source, destination) == ConversionKind.ExplicitNumeric;
}
private static bool IsConstantNumericZero(ConstantValue value)
{
switch (value.Discriminator)
{
case ConstantValueTypeDiscriminator.SByte:
return value.SByteValue == 0;
case ConstantValueTypeDiscriminator.Byte:
return value.ByteValue == 0;
case ConstantValueTypeDiscriminator.Int16:
return value.Int16Value == 0;
case ConstantValueTypeDiscriminator.Int32:
case ConstantValueTypeDiscriminator.NInt:
return value.Int32Value == 0;
case ConstantValueTypeDiscriminator.Int64:
return value.Int64Value == 0;
case ConstantValueTypeDiscriminator.UInt16:
return value.UInt16Value == 0;
case ConstantValueTypeDiscriminator.UInt32:
case ConstantValueTypeDiscriminator.NUInt:
return value.UInt32Value == 0;
case ConstantValueTypeDiscriminator.UInt64:
return value.UInt64Value == 0;
case ConstantValueTypeDiscriminator.Single:
case ConstantValueTypeDiscriminator.Double:
return value.DoubleValue == 0;
case ConstantValueTypeDiscriminator.Decimal:
return value.DecimalValue == 0;
}
return false;
}
private static bool IsNumericType(TypeSymbol type)
{
switch (type.SpecialType)
{
case SpecialType.System_Char:
case SpecialType.System_SByte:
case SpecialType.System_Byte:
case SpecialType.System_Int16:
case SpecialType.System_UInt16:
case SpecialType.System_Int32:
case SpecialType.System_UInt32:
case SpecialType.System_Int64:
case SpecialType.System_UInt64:
case SpecialType.System_Single:
case SpecialType.System_Double:
case SpecialType.System_Decimal:
case SpecialType.System_IntPtr when type.IsNativeIntegerType:
case SpecialType.System_UIntPtr when type.IsNativeIntegerType:
return true;
default:
return false;
}
}
private static bool HasSpecialIntPtrConversion(TypeSymbol source, TypeSymbol target)
{
Debug.Assert((object)source != null);
Debug.Assert((object)target != null);
// There are only a total of twelve user-defined explicit conversions on IntPtr and UIntPtr:
//
// IntPtr <---> int
// IntPtr <---> long
// IntPtr <---> void*
// UIntPtr <---> uint
// UIntPtr <---> ulong
// UIntPtr <---> void*
//
// The specification says that you can put any *standard* implicit or explicit conversion
// on "either side" of a user-defined explicit conversion, so the specification allows, say,
// UIntPtr --> byte because the conversion UIntPtr --> uint is user-defined and the
// conversion uint --> byte is "standard". It is "standard" because the conversion
// byte --> uint is an implicit numeric conversion.
// This means that certain conversions should be illegal. For example, IntPtr --> ulong
// should be illegal because none of int --> ulong, long --> ulong and void* --> ulong
// are "standard" conversions.
// Similarly, some conversions involving IntPtr should be illegal because they are
// ambiguous. byte --> IntPtr?, for example, is ambiguous. (There are four possible
// UD operators: int --> IntPtr and long --> IntPtr, and their lifted versions. The
// best possible source type is int, the best possible target type is IntPtr?, and
// there is an ambiguity between the unlifted int --> IntPtr, and the lifted
// int? --> IntPtr? conversions.)
// In practice, the native compiler, and hence, the Roslyn compiler, allows all
// these conversions. Any conversion from a numeric type to IntPtr, or from an IntPtr
// to a numeric type, is allowed. Also, any conversion from a pointer type to IntPtr
// or vice versa is allowed.
var s0 = source.StrippedType();
var t0 = target.StrippedType();
TypeSymbol otherType;
if (isIntPtrOrUIntPtr(s0))
{
otherType = t0;
}
else if (isIntPtrOrUIntPtr(t0))
{
otherType = s0;
}
else
{
return false;
}
if (otherType.IsPointerOrFunctionPointer())
{
return true;
}
if (otherType.TypeKind == TypeKind.Enum)
{
return true;
}
switch (otherType.SpecialType)
{
case SpecialType.System_SByte:
case SpecialType.System_Byte:
case SpecialType.System_Int16:
case SpecialType.System_UInt16:
case SpecialType.System_Char:
case SpecialType.System_Int32:
case SpecialType.System_UInt32:
case SpecialType.System_Int64:
case SpecialType.System_UInt64:
case SpecialType.System_Double:
case SpecialType.System_Single:
case SpecialType.System_Decimal:
return true;
}
return false;
static bool isIntPtrOrUIntPtr(TypeSymbol type) =>
(type.SpecialType == SpecialType.System_IntPtr || type.SpecialType == SpecialType.System_UIntPtr) && !type.IsNativeIntegerType;
}
private static bool HasExplicitEnumerationConversion(TypeSymbol source, TypeSymbol destination)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
// SPEC: The explicit enumeration conversions are:
// SPEC: From sbyte, byte, short, ushort, int, uint, long, ulong, nint, nuint, char, float, double, or decimal to any enum-type.
// SPEC: From any enum-type to sbyte, byte, short, ushort, int, uint, long, ulong, nint, nuint, char, float, double, or decimal.
// SPEC: From any enum-type to any other enum-type.
if (IsNumericType(source) && destination.IsEnumType())
{
return true;
}
if (IsNumericType(destination) && source.IsEnumType())
{
return true;
}
if (source.IsEnumType() && destination.IsEnumType())
{
return true;
}
return false;
}
#nullable disable
private Conversion ClassifyImplicitNullableConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
// SPEC: Predefined implicit conversions that operate on non-nullable value types can also be used with
// SPEC: nullable forms of those types. For each of the predefined implicit identity, numeric and tuple conversions
// SPEC: that convert from a non-nullable value type S to a non-nullable value type T, the following implicit
// SPEC: nullable conversions exist:
// SPEC: * An implicit conversion from S? to T?.
// SPEC: * An implicit conversion from S to T?.
if (!destination.IsNullableType())
{
return Conversion.NoConversion;
}
TypeSymbol unwrappedDestination = destination.GetNullableUnderlyingType();
TypeSymbol unwrappedSource = source.StrippedType();
if (!unwrappedSource.IsValueType)
{
return Conversion.NoConversion;
}
if (HasIdentityConversionInternal(unwrappedSource, unwrappedDestination))
{
return Conversion.ImplicitNullableWithIdentityUnderlying;
}
if (HasImplicitNumericConversion(unwrappedSource, unwrappedDestination))
{
return Conversion.ImplicitNullableWithImplicitNumericUnderlying;
}
var tupleConversion = ClassifyImplicitTupleConversion(unwrappedSource, unwrappedDestination, ref useSiteInfo);
if (tupleConversion.Exists)
{
return new Conversion(ConversionKind.ImplicitNullable, ImmutableArray.Create(tupleConversion));
}
return Conversion.NoConversion;
}
private delegate Conversion ClassifyConversionFromExpressionDelegate(ConversionsBase conversions, BoundExpression sourceExpression, TypeWithAnnotations destination, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, bool forCast);
private delegate Conversion ClassifyConversionFromTypeDelegate(ConversionsBase conversions, TypeWithAnnotations source, TypeWithAnnotations destination, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, bool forCast);
private Conversion GetImplicitTupleLiteralConversion(BoundTupleLiteral source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert(Compilation is not null);
// GetTupleLiteralConversion is not used with IncludeNullability currently.
// If that changes, the delegate below will need to consider top-level nullability.
Debug.Assert(!IncludeNullability);
return GetTupleLiteralConversion(
source,
destination,
ref useSiteInfo,
ConversionKind.ImplicitTupleLiteral,
(ConversionsBase conversions, BoundExpression s, TypeWithAnnotations d, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> u, bool forCast)
=> conversions.ClassifyImplicitConversionFromExpression(s, d.Type, ref u),
isChecked: false,
forCast: false);
}
private Conversion GetExplicitTupleLiteralConversion(BoundTupleLiteral source, TypeSymbol destination, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, bool forCast)
{
Debug.Assert(Compilation is not null);
// GetTupleLiteralConversion is not used with IncludeNullability currently.
// If that changes, the delegate below will need to consider top-level nullability.
Debug.Assert(!IncludeNullability);
return GetTupleLiteralConversion(
source,
destination,
ref useSiteInfo,
ConversionKind.ExplicitTupleLiteral,
(ConversionsBase conversions, BoundExpression s, TypeWithAnnotations d, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> u, bool forCast) =>
conversions.ClassifyConversionFromExpression(s, d.Type, isChecked: isChecked, ref u, forCast: forCast),
isChecked: isChecked,
forCast: forCast);
}
private Conversion GetTupleLiteralConversion(
BoundTupleLiteral source,
TypeSymbol destination,
ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo,
ConversionKind kind,
ClassifyConversionFromExpressionDelegate classifyConversion,
bool isChecked,
bool forCast)
{
Debug.Assert(Compilation is not null);
var arguments = source.Arguments;
// check if the type is actually compatible type for a tuple of given cardinality
if (!destination.IsTupleTypeOfCardinality(arguments.Length))
{
return Conversion.NoConversion;
}
var targetElementTypes = destination.TupleElementTypesWithAnnotations;
Debug.Assert(arguments.Length == targetElementTypes.Length);
// check arguments against flattened list of target element types
var argumentConversions = ArrayBuilder<Conversion>.GetInstance(arguments.Length);
for (int i = 0; i < arguments.Length; i++)
{
var argument = arguments[i];
var result = classifyConversion(this, argument, targetElementTypes[i], isChecked: isChecked, ref useSiteInfo, forCast: forCast);
if (!result.Exists)
{
argumentConversions.Free();
return Conversion.NoConversion;
}
argumentConversions.Add(result);
}
return new Conversion(kind, argumentConversions.ToImmutableAndFree());
}
private Conversion ClassifyImplicitTupleConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
return ClassifyTupleConversion(
source,
destination,
ref useSiteInfo,
ConversionKind.ImplicitTuple,
(ConversionsBase conversions, TypeWithAnnotations s, TypeWithAnnotations d, bool _, ref CompoundUseSiteInfo<AssemblySymbol> u, bool _) =>
{
if (!conversions.HasTopLevelNullabilityImplicitConversion(s, d))
{
return Conversion.NoConversion;
}
return conversions.ClassifyImplicitConversionFromType(s.Type, d.Type, ref u);
},
isChecked: false,
forCast: false);
}
private Conversion ClassifyExplicitTupleConversion(TypeSymbol source, TypeSymbol destination, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, bool forCast)
{
return ClassifyTupleConversion(
source,
destination,
ref useSiteInfo,
ConversionKind.ExplicitTuple,
(ConversionsBase conversions, TypeWithAnnotations s, TypeWithAnnotations d, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> u, bool forCast) =>
{
if (!conversions.HasTopLevelNullabilityImplicitConversion(s, d))
{
return Conversion.NoConversion;
}
return conversions.ClassifyConversionFromType(s.Type, d.Type, isChecked: isChecked, ref u, forCast);
},
isChecked: isChecked,
forCast);
}
private Conversion ClassifyTupleConversion(
TypeSymbol source,
TypeSymbol destination,
ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo,
ConversionKind kind,
ClassifyConversionFromTypeDelegate classifyConversion,
bool isChecked,
bool forCast)
{
ImmutableArray<TypeWithAnnotations> sourceTypes;
ImmutableArray<TypeWithAnnotations> destTypes;
if (!source.TryGetElementTypesWithAnnotationsIfTupleType(out sourceTypes) ||
!destination.TryGetElementTypesWithAnnotationsIfTupleType(out destTypes) ||
sourceTypes.Length != destTypes.Length)
{
return Conversion.NoConversion;
}
var nestedConversions = ArrayBuilder<Conversion>.GetInstance(sourceTypes.Length);
for (int i = 0; i < sourceTypes.Length; i++)
{
var conversion = classifyConversion(this, sourceTypes[i], destTypes[i], isChecked: isChecked, ref useSiteInfo, forCast);
if (!conversion.Exists)
{
nestedConversions.Free();
return Conversion.NoConversion;
}
nestedConversions.Add(conversion);
}
return new Conversion(kind, nestedConversions.ToImmutableAndFree());
}
private Conversion ClassifyExplicitNullableConversion(TypeSymbol source, TypeSymbol destination, bool isChecked, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, bool forCast)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
// SPEC: Explicit nullable conversions permit predefined explicit conversions that operate on
// SPEC: non-nullable value types to also be used with nullable forms of those types. For
// SPEC: each of the predefined explicit conversions that convert from a non-nullable value type
// SPEC: S to a non-nullable value type T, the following nullable conversions exist:
// SPEC: An explicit conversion from S? to T?.
// SPEC: An explicit conversion from S to T?.
// SPEC: An explicit conversion from S? to T.
if (!source.IsNullableType() && !destination.IsNullableType())
{
return Conversion.NoConversion;
}
TypeSymbol unwrappedSource = source.StrippedType();
TypeSymbol unwrappedDestination = destination.StrippedType();
if (HasIdentityConversionInternal(unwrappedSource, unwrappedDestination))
{
return Conversion.ExplicitNullableWithIdentityUnderlying;
}
if (HasImplicitNumericConversion(unwrappedSource, unwrappedDestination))
{
return Conversion.ExplicitNullableWithImplicitNumericUnderlying;
}
if (HasExplicitNumericConversion(unwrappedSource, unwrappedDestination))
{
return Conversion.ExplicitNullableWithExplicitNumericUnderlying;
}
var tupleConversion = ClassifyExplicitTupleConversion(unwrappedSource, unwrappedDestination, isChecked: isChecked, ref useSiteInfo, forCast);
if (tupleConversion.Exists)
{
return new Conversion(ConversionKind.ExplicitNullable, ImmutableArray.Create(tupleConversion));
}
if (HasExplicitEnumerationConversion(unwrappedSource, unwrappedDestination))
{
return Conversion.ExplicitNullableWithExplicitEnumerationUnderlying;
}
if (HasPointerToIntegerConversion(unwrappedSource, unwrappedDestination))
{
return Conversion.ExplicitNullableWithPointerToIntegerUnderlying;
}
return Conversion.NoConversion;
}
private bool HasCovariantArrayConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
var s = source as ArrayTypeSymbol;
var d = destination as ArrayTypeSymbol;
if ((object)s == null || (object)d == null)
{
return false;
}
// * S and T differ only in element type. In other words, S and T have the same number of dimensions.
if (!s.HasSameShapeAs(d))
{
return false;
}
// * Both SE and TE are reference types.
// * An implicit reference conversion exists from SE to TE.
return HasImplicitReferenceConversion(s.ElementTypeWithAnnotations, d.ElementTypeWithAnnotations, ref useSiteInfo);
}
public bool HasIdentityOrImplicitReferenceConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
if (HasIdentityConversionInternal(source, destination))
{
return true;
}
return HasImplicitReferenceConversion(source, destination, ref useSiteInfo);
}
private static bool HasImplicitDynamicConversionFromExpression(TypeSymbol expressionType, TypeSymbol destination)
{
// Spec (§6.1.8)
// An implicit dynamic conversion exists from an expression of type dynamic to any type T.
Debug.Assert((object)destination != null);
return expressionType?.Kind == SymbolKind.DynamicType && !destination.IsPointerOrFunctionPointer();
}
private static bool HasExplicitDynamicConversion(TypeSymbol source, TypeSymbol destination)
{
// SPEC: An explicit dynamic conversion exists from an expression of [sic] type dynamic to any type T.
// ISSUE: The "an expression of" part of the spec is probably an error; see https://github.com/dotnet/csharplang/issues/132
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
return source.Kind == SymbolKind.DynamicType && !destination.IsPointerOrFunctionPointer();
}
private bool HasArrayConversionToInterface(ArrayTypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
if (!source.IsSZArray)
{
return false;
}
if (!destination.IsInterfaceType())
{
return false;
}
// The specification says that there is a conversion:
// * From a single-dimensional array type S[] to IList<T> and its base
// interfaces, provided that there is an implicit identity or reference
// conversion from S to T.
//
// Newer versions of the framework also have arrays be convertible to
// IReadOnlyList<T> and IReadOnlyCollection<T>; we honor that as well.
//
// Therefore we must check for:
//
// IList<T>
// ICollection<T>
// IEnumerable<T>
// IEnumerable
// IReadOnlyList<T>
// IReadOnlyCollection<T>
if (destination.SpecialType == SpecialType.System_Collections_IEnumerable)
{
return true;
}
NamedTypeSymbol destinationAgg = (NamedTypeSymbol)destination;
if (destinationAgg.AllTypeArgumentCount() != 1)
{
return false;
}
if (!destinationAgg.IsPossibleArrayGenericInterface())
{
return false;
}
TypeWithAnnotations elementType = source.ElementTypeWithAnnotations;
TypeWithAnnotations argument0 = destinationAgg.TypeArgumentWithDefinitionUseSiteDiagnostics(0, ref useSiteInfo);
if (IncludeNullability && !HasTopLevelNullabilityImplicitConversion(elementType, argument0))
{
return false;
}
return HasIdentityOrImplicitReferenceConversion(elementType.Type, argument0.Type, ref useSiteInfo);
}
private bool HasImplicitReferenceConversion(TypeWithAnnotations source, TypeWithAnnotations destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
if (IncludeNullability)
{
if (!HasTopLevelNullabilityImplicitConversion(source, destination))
{
return false;
}
// Check for identity conversion of underlying types if the top-level nullability is distinct.
// (An identity conversion where nullability matches is not considered an implicit reference conversion.)
if (source.NullableAnnotation != destination.NullableAnnotation &&
HasIdentityConversionInternal(source.Type, destination.Type, includeNullability: true))
{
return true;
}
}
return HasImplicitReferenceConversion(source.Type, destination.Type, ref useSiteInfo);
}
#nullable enable
internal bool HasImplicitReferenceConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
if (source.IsErrorType())
{
return false;
}
if (!source.IsReferenceType)
{
return false;
}
// SPEC: The implicit reference conversions are:
// SPEC: UNDONE: From any reference-type to a reference-type T if it has an implicit identity
// SPEC: UNDONE: or reference conversion to a reference-type T0 and T0 has an identity conversion to T.
// UNDONE: Is the right thing to do here to strip dynamic off and check for convertibility?
// SPEC: From any reference type to object and dynamic.
if (destination.SpecialType == SpecialType.System_Object || destination.Kind == SymbolKind.DynamicType)
{
return true;
}
switch (source.TypeKind)
{
case TypeKind.Class:
// SPEC: From any class type S to any class type T provided S is derived from T.
if (destination.IsClassType() && IsBaseClass(source, destination, ref useSiteInfo))
{
return true;
}
return HasImplicitConversionToInterface(source, destination, ref useSiteInfo);
case TypeKind.Interface:
// SPEC: From any interface-type S to any interface-type T, provided S is derived from T.
// NOTE: This handles variance conversions
return HasImplicitConversionToInterface(source, destination, ref useSiteInfo);
case TypeKind.Delegate:
// SPEC: From any delegate-type to System.Delegate and the interfaces it implements.
// NOTE: This handles variance conversions.
return HasImplicitConversionFromDelegate(source, destination, ref useSiteInfo);
case TypeKind.TypeParameter:
return HasImplicitReferenceTypeParameterConversion((TypeParameterSymbol)source, destination, ref useSiteInfo);
case TypeKind.Array:
// SPEC: From an array-type S ... to an array-type T, provided ...
// SPEC: From any array-type to System.Array and the interfaces it implements.
// SPEC: From a single-dimensional array type S[] to IList<T>, provided ...
return HasImplicitConversionFromArray(source, destination, ref useSiteInfo);
}
// UNDONE: Implicit conversions involving type parameters that are known to be reference types.
return false;
}
private bool HasImplicitConversionToInterface(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
if (!destination.IsInterfaceType())
{
return false;
}
// * From any class type S to any interface type T provided S implements an interface
// convertible to T.
if (source.IsClassType())
{
return HasAnyBaseInterfaceConversion(source, destination, ref useSiteInfo);
}
// * From any interface type S to any interface type T provided S implements an interface
// convertible to T.
// * From any interface type S to any interface type T provided S is not T and S is
// an interface convertible to T.
if (source.IsInterfaceType())
{
if (HasAnyBaseInterfaceConversion(source, destination, ref useSiteInfo))
{
return true;
}
if (!HasIdentityConversionInternal(source, destination) && HasInterfaceVarianceConversion(source, destination, ref useSiteInfo))
{
return true;
}
}
return false;
}
private bool HasImplicitConversionFromArray(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
var s = source as ArrayTypeSymbol;
if (s is null)
{
return false;
}
// * From an array type S with an element type SE to an array type T with element type TE
// provided that all of the following are true:
// * S and T differ only in element type. In other words, S and T have the same number of dimensions.
// * Both SE and TE are reference types.
// * An implicit reference conversion exists from SE to TE.
if (HasCovariantArrayConversion(source, destination, ref useSiteInfo))
{
return true;
}
// * From any array type to System.Array or any interface implemented by System.Array.
if (destination.GetSpecialTypeSafe() == SpecialType.System_Array)
{
return true;
}
if (IsBaseInterface(destination, this.corLibrary.GetDeclaredSpecialType(SpecialType.System_Array), ref useSiteInfo))
{
return true;
}
// * From a single-dimensional array type S[] to IList<T> and its base
// interfaces, provided that there is an implicit identity or reference
// conversion from S to T.
if (HasArrayConversionToInterface(s, destination, ref useSiteInfo))
{
return true;
}
return false;
}
private bool HasImplicitConversionFromDelegate(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
if (!source.IsDelegateType())
{
return false;
}
// * From any delegate type to System.Delegate
//
// SPEC OMISSION:
//
// The spec should actually say
//
// * From any delegate type to System.Delegate
// * From any delegate type to System.MulticastDelegate
// * From any delegate type to any interface implemented by System.MulticastDelegate
var specialDestination = destination.GetSpecialTypeSafe();
if (specialDestination == SpecialType.System_MulticastDelegate ||
specialDestination == SpecialType.System_Delegate ||
IsBaseInterface(destination, this.corLibrary.GetDeclaredSpecialType(SpecialType.System_MulticastDelegate), ref useSiteInfo))
{
return true;
}
// * From any delegate type S to a delegate type T provided S is not T and
// S is a delegate convertible to T
if (HasDelegateVarianceConversion(source, destination, ref useSiteInfo))
{
return true;
}
return false;
}
private bool HasImplicitFunctionTypeConversion(FunctionTypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
if (destination is FunctionTypeSymbol destinationFunctionType)
{
return HasImplicitFunctionTypeToFunctionTypeConversion(source, destinationFunctionType, ref useSiteInfo);
}
return IsValidFunctionTypeConversionTarget(destination, ref useSiteInfo) &&
source.GetInternalDelegateType() is { };
}
internal bool IsValidFunctionTypeConversionTarget(TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
if (destination.SpecialType == SpecialType.System_MulticastDelegate)
{
return true;
}
if (destination.IsNonGenericExpressionType())
{
return true;
}
var derivedType = this.corLibrary.GetDeclaredSpecialType(SpecialType.System_MulticastDelegate);
if (IsBaseClass(derivedType, destination, ref useSiteInfo) ||
IsBaseInterface(destination, derivedType, ref useSiteInfo))
{
return true;
}
return false;
}
private bool HasImplicitFunctionTypeToFunctionTypeConversion(FunctionTypeSymbol sourceType, FunctionTypeSymbol destinationType, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
var sourceDelegate = sourceType.GetInternalDelegateType();
if (sourceDelegate is null)
{
return false;
}
var destinationDelegate = destinationType.GetInternalDelegateType();
if (destinationDelegate is null)
{
return false;
}
// https://github.com/dotnet/roslyn/issues/55909: We're relying on the variance of
// FunctionTypeSymbol.GetInternalDelegateType() which fails for synthesized
// delegate types where the type parameters are invariant.
return HasDelegateVarianceConversion(sourceDelegate, destinationDelegate, ref useSiteInfo);
}
#nullable disable
public bool HasImplicitTypeParameterConversion(TypeParameterSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
if (HasImplicitReferenceTypeParameterConversion(source, destination, ref useSiteInfo))
{
return true;
}
if (HasImplicitBoxingTypeParameterConversion(source, destination, ref useSiteInfo))
{
return true;
}
if (destination is TypeParameterSymbol { AllowsRefLikeType: false } &&
!source.AllowsRefLikeType &&
source.DependsOn((TypeParameterSymbol)destination))
{
return true;
}
return false;
}
private bool HasImplicitReferenceTypeParameterConversion(TypeParameterSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
if (source.IsValueType)
{
return false; // Not a reference conversion.
}
if (source.AllowsRefLikeType)
{
return false;
}
// The following implicit conversions exist for a given type parameter T:
//
// * From T to its effective base class C.
// * From T to any base class of C.
// * From T to any interface implemented by C (or any interface variance-compatible with such)
if (HasImplicitEffectiveBaseConversion(source, destination, ref useSiteInfo))
{
return true;
}
// * From T to any interface type I in T's effective interface set, and
// from T to any base interface of I (or any interface variance-compatible with such)
if (HasImplicitEffectiveInterfaceSetConversion(source, destination, ref useSiteInfo))
{
return true;
}
// * From T to a type parameter U, provided T depends on U.
if (destination is TypeParameterSymbol { AllowsRefLikeType: false } &&
source.DependsOn((TypeParameterSymbol)destination))
{
return true;
}
return false;
}
// Spec 6.1.10: Implicit conversions involving type parameters
private bool HasImplicitEffectiveBaseConversion(TypeParameterSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
// * From T to its effective base class C.
var effectiveBaseClass = source.EffectiveBaseClass(ref useSiteInfo);
if (HasIdentityConversionInternal(effectiveBaseClass, destination))
{
return true;
}
// * From T to any base class of C.
if (IsBaseClass(effectiveBaseClass, destination, ref useSiteInfo))
{
return true;
}
// * From T to any interface implemented by C (or any interface variance-compatible with such)
if (HasAnyBaseInterfaceConversion(effectiveBaseClass, destination, ref useSiteInfo))
{
return true;
}
return false;
}
private bool HasImplicitEffectiveInterfaceSetConversion(TypeParameterSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
return HasVarianceCompatibleInterfaceInEffectiveInterfaceSet(source, destination, ref useSiteInfo);
}
private bool HasVarianceCompatibleInterfaceInEffectiveInterfaceSet(TypeParameterSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
if (!destination.IsInterfaceType())
{
return false;
}
// * From T to any interface type I in T's effective interface set, and
// from T to any base interface of I (or any interface variance-compatible with such)
foreach (var i in source.AllEffectiveInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteInfo))
{
if (HasInterfaceVarianceConversion(i, destination, ref useSiteInfo))
{
return true;
}
}
return false;
}
private bool HasAnyBaseInterfaceConversion(TypeSymbol derivedType, TypeSymbol baseType, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
return ImplementsVarianceCompatibleInterface(derivedType, baseType, ref useSiteInfo);
}
private bool ImplementsVarianceCompatibleInterface(TypeSymbol derivedType, TypeSymbol baseType, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)derivedType != null);
Debug.Assert((object)baseType != null);
if (!baseType.IsInterfaceType())
{
return false;
}
var d = derivedType as NamedTypeSymbol;
if ((object)d == null)
{
return false;
}
foreach (var i in d.AllInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteInfo))
{
if (HasInterfaceVarianceConversion(i, baseType, ref useSiteInfo))
{
return true;
}
}
return false;
}
internal bool ImplementsVarianceCompatibleInterface(NamedTypeSymbol derivedType, TypeSymbol baseType, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
return ImplementsVarianceCompatibleInterface((TypeSymbol)derivedType, baseType, ref useSiteInfo);
}
internal bool HasImplicitConversionToOrImplementsVarianceCompatibleInterface(TypeSymbol typeToCheck, NamedTypeSymbol targetInterfaceType, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, out bool needSupportForRefStructInterfaces)
{
Debug.Assert(targetInterfaceType.IsErrorType() || targetInterfaceType.IsInterface);
if (ClassifyImplicitConversionFromType(typeToCheck, targetInterfaceType, ref useSiteInfo).IsImplicit)
{
needSupportForRefStructInterfaces = false;
return true;
}
if (IsRefLikeOrAllowsRefLikeTypeImplementingVarianceCompatibleInterface(typeToCheck, targetInterfaceType, ref useSiteInfo))
{
needSupportForRefStructInterfaces = true;
return true;
}
needSupportForRefStructInterfaces = false;
return false;
}
private bool IsRefLikeOrAllowsRefLikeTypeImplementingVarianceCompatibleInterface(TypeSymbol typeToCheck, NamedTypeSymbol targetInterfaceType, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
if (typeToCheck is TypeParameterSymbol typeParameter)
{
return typeParameter.AllowsRefLikeType && HasVarianceCompatibleInterfaceInEffectiveInterfaceSet(typeParameter, targetInterfaceType, ref useSiteInfo);
}
else if (typeToCheck.IsRefLikeType)
{
return ImplementsVarianceCompatibleInterface(typeToCheck, targetInterfaceType, ref useSiteInfo);
}
return false;
}
internal bool HasImplicitConversionToOrImplementsVarianceCompatibleInterface(BoundExpression expressionToCheck, NamedTypeSymbol targetInterfaceType, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, out bool needSupportForRefStructInterfaces)
{
Debug.Assert(targetInterfaceType.IsErrorType() || targetInterfaceType.IsInterface);
if (ClassifyImplicitConversionFromExpression(expressionToCheck, targetInterfaceType, ref useSiteInfo).IsImplicit)
{
needSupportForRefStructInterfaces = false;
return true;
}
if (expressionToCheck.Type is TypeSymbol typeToCheck && IsRefLikeOrAllowsRefLikeTypeImplementingVarianceCompatibleInterface(typeToCheck, targetInterfaceType, ref useSiteInfo))
{
needSupportForRefStructInterfaces = true;
return true;
}
needSupportForRefStructInterfaces = false;
return false;
}
////////////////////////////////////////////////////////////////////////////////
// The rules for variant interface and delegate conversions are the same:
//
// An interface/delegate type S is convertible to an interface/delegate type T
// if and only if S is U<S1, ... Sn> and T is U<T1, ... Tn> such that for all
// parameters of U:
//
// * if the ith parameter of U is invariant then Si is exactly equal to Ti.
// * if the ith parameter of U is covariant then either Si is exactly equal
// to Ti, or there is an implicit reference conversion from Si to Ti.
// * if the ith parameter of U is contravariant then either Si is exactly
// equal to Ti, or there is an implicit reference conversion from Ti to Si.
#nullable enable
private bool HasInterfaceVarianceConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
NamedTypeSymbol? s = source as NamedTypeSymbol;
NamedTypeSymbol? d = destination as NamedTypeSymbol;
if (s is null || d is null)
{
return false;
}
if (!s.IsInterfaceType() || !d.IsInterfaceType())
{
return false;
}
return HasVariantConversion(s, d, ref useSiteInfo);
}
private bool HasDelegateVarianceConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
NamedTypeSymbol? s = source as NamedTypeSymbol;
NamedTypeSymbol? d = destination as NamedTypeSymbol;
if (s is null || d is null)
{
return false;
}
if (!s.IsDelegateType() || !d.IsDelegateType())
{
return false;
}
return HasVariantConversion(s, d, ref useSiteInfo);
}
private bool HasVariantConversion(NamedTypeSymbol source, NamedTypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
// We check for overflows in HasVariantConversion, because they are only an issue
// in the presence of contravariant type parameters, which are not involved in
// most conversions.
// See VarianceTests for examples (e.g. TestVarianceConversionCycle,
// TestVarianceConversionInfiniteExpansion).
//
// CONSIDER: A more rigorous solution would mimic the CLI approach, which uses
// a combination of requiring finite instantiation closures (see section 9.2 of
// the CLI spec) and records previous conversion steps to check for cycles.
if (currentRecursionDepth >= MaximumRecursionDepth)
{
// NOTE: The spec doesn't really address what happens if there's an overflow
// in our conversion check. It's sort of implied that the conversion "proof"
// should be finite, so we'll just say that no conversion is possible.
return false;
}
// Do a quick check up front to avoid instantiating a new Conversions object,
// if possible.
var quickResult = HasVariantConversionQuick(source, destination);
if (quickResult.HasValue())
{
return quickResult.Value();
}
return this.CreateInstance(currentRecursionDepth + 1).
HasVariantConversionNoCycleCheck(source, destination, ref useSiteInfo);
}
private ThreeState HasVariantConversionQuick(NamedTypeSymbol source, NamedTypeSymbol destination)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
if (HasIdentityConversionInternal(source, destination))
{
return ThreeState.True;
}
NamedTypeSymbol typeSymbol = source.OriginalDefinition;
if (!TypeSymbol.Equals(typeSymbol, destination.OriginalDefinition, TypeCompareKind.ConsiderEverything2))
{
return ThreeState.False;
}
return ThreeState.Unknown;
}
private bool HasVariantConversionNoCycleCheck(NamedTypeSymbol source, NamedTypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
var typeParameters = ArrayBuilder<TypeWithAnnotations>.GetInstance();
var sourceTypeArguments = ArrayBuilder<TypeWithAnnotations>.GetInstance();
var destinationTypeArguments = ArrayBuilder<TypeWithAnnotations>.GetInstance();
try
{
source.OriginalDefinition.GetAllTypeArguments(typeParameters, ref useSiteInfo);
source.GetAllTypeArguments(sourceTypeArguments, ref useSiteInfo);
destination.GetAllTypeArguments(destinationTypeArguments, ref useSiteInfo);
Debug.Assert(TypeSymbol.Equals(source.OriginalDefinition, destination.OriginalDefinition, TypeCompareKind.AllIgnoreOptions));
Debug.Assert(typeParameters.Count == sourceTypeArguments.Count);
Debug.Assert(typeParameters.Count == destinationTypeArguments.Count);
for (int paramIndex = 0; paramIndex < typeParameters.Count; ++paramIndex)
{
var sourceTypeArgument = sourceTypeArguments[paramIndex];
var destinationTypeArgument = destinationTypeArguments[paramIndex];
// If they're identical then this one is automatically good, so skip it.
if (HasIdentityConversionInternal(sourceTypeArgument.Type, destinationTypeArgument.Type) &&
HasTopLevelNullabilityIdentityConversion(sourceTypeArgument, destinationTypeArgument))
{
continue;
}
TypeParameterSymbol typeParameterSymbol = (TypeParameterSymbol)typeParameters[paramIndex].Type;
switch (typeParameterSymbol.Variance)
{
case VarianceKind.None:
// System.IEquatable<T> is invariant for back compat reasons (dynamic type checks could start
// to succeed where they previously failed, creating different runtime behavior), but the uses
// require treatment specifically of nullability as contravariant, so we special case the
// behavior here. Normally we use GetWellKnownType for these kinds of checks, but in this
// case we don't want just the canonical IEquatable to be special-cased, we want all definitions
// to be treated as contravariant, in case there are other definitions in metadata that were
// compiled with that expectation.
if (isTypeIEquatable(destination.OriginalDefinition) &&
TypeSymbol.Equals(destinationTypeArgument.Type, sourceTypeArgument.Type, TypeCompareKind.AllNullableIgnoreOptions) &&
HasAnyNullabilityImplicitConversion(destinationTypeArgument, sourceTypeArgument))
{
return true;
}
return false;
case VarianceKind.Out:
if (!HasImplicitReferenceConversion(sourceTypeArgument, destinationTypeArgument, ref useSiteInfo))
{
return false;
}
break;
case VarianceKind.In:
if (!HasImplicitReferenceConversion(destinationTypeArgument, sourceTypeArgument, ref useSiteInfo))
{
return false;
}
break;
default:
throw ExceptionUtilities.UnexpectedValue(typeParameterSymbol.Variance);
}
}
}
finally
{
typeParameters.Free();
sourceTypeArguments.Free();
destinationTypeArguments.Free();
}
return true;
static bool isTypeIEquatable(NamedTypeSymbol type)
{
return type is
{
IsInterface: true,
Name: "IEquatable",
ContainingNamespace: { Name: "System", ContainingNamespace: { IsGlobalNamespace: true } },
ContainingSymbol: { Kind: SymbolKind.Namespace },
TypeParameters: { Length: 1 }
};
}
}
// Spec 6.1.10
private bool HasImplicitBoxingTypeParameterConversion(TypeParameterSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
if (source.IsReferenceType)
{
return false; // Not a boxing conversion; both source and destination are references.
}
if (source.AllowsRefLikeType)
{
return false;
}
// The following implicit conversions exist for a given type parameter T:
//
// * From T to its effective base class C.
// * From T to any base class of C.
// * From T to any interface implemented by C (or any interface variance-compatible with such)
if (HasImplicitEffectiveBaseConversion(source, destination, ref useSiteInfo))
{
return true;
}
// * From T to any interface type I in T's effective interface set, and
// from T to any base interface of I (or any interface variance-compatible with such)
if (HasImplicitEffectiveInterfaceSetConversion(source, destination, ref useSiteInfo))
{
return true;
}
// SPEC: From T to a type parameter U, provided T depends on U
if (destination is TypeParameterSymbol { AllowsRefLikeType: false } d &&
source.DependsOn(d))
{
return true;
}
// SPEC: From T to a reference type I if it has an implicit conversion to a reference
// SPEC: type S0 and S0 has an identity conversion to S. At run-time the conversion
// SPEC: is executed the same way as the conversion to S0.
// REVIEW: If T is not known to be a reference type then the only way this clause can
// REVIEW: come into effect is if the target type is dynamic. Is that correct?
if (destination.Kind == SymbolKind.DynamicType)
{
return true;
}
return false;
}
public bool HasBoxingConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
// Certain type parameter conversions are classified as boxing conversions.
if ((source.TypeKind == TypeKind.TypeParameter) &&
HasImplicitBoxingTypeParameterConversion((TypeParameterSymbol)source, destination, ref useSiteInfo))
{
return true;
}
// The rest of the boxing conversions only operate when going from a value type to a
// reference type.
if (!source.IsValueType || !destination.IsReferenceType)
{
return false;
}
// A boxing conversion exists from a nullable type to a reference type if and only if a
// boxing conversion exists from the underlying type.
if (source.IsNullableType())
{
return HasBoxingConversion(source.GetNullableUnderlyingType(), destination, ref useSiteInfo);
}
// A boxing conversion exists from any non-nullable value type to object and dynamic, to
// System.ValueType, and to any interface type variance-compatible with one implemented
// by the non-nullable value type.
// Furthermore, an enum type can be converted to the type System.Enum.
// We set the base class of the structs to System.ValueType, System.Enum, etc, so we can
// just check here.
// There are a couple of exceptions. The very special types ArgIterator, ArgumentHandle and
// TypedReference are not boxable:
if (source.IsRestrictedType())
{
return false;
}
if (destination.Kind == SymbolKind.DynamicType)
{
return !source.IsPointerOrFunctionPointer();
}
if (IsBaseClass(source, destination, ref useSiteInfo))
{
return true;
}
if (HasAnyBaseInterfaceConversion(source, destination, ref useSiteInfo))
{
return true;
}
return false;
}
internal static bool HasImplicitPointerToVoidConversion(TypeSymbol source, TypeSymbol destination)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
// SPEC: The set of implicit conversions is extended to include...
// SPEC: ... from any pointer type to the type void*.
return source.IsPointerOrFunctionPointer() && destination is PointerTypeSymbol { PointedAtType: { SpecialType: SpecialType.System_Void } };
}
internal bool HasImplicitPointerConversion(TypeSymbol? source, TypeSymbol? destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
if (!(source is FunctionPointerTypeSymbol { Signature: { } sourceSig })
|| !(destination is FunctionPointerTypeSymbol { Signature: { } destinationSig }))
{
return false;
}
if (sourceSig.ParameterCount != destinationSig.ParameterCount ||
sourceSig.CallingConvention != destinationSig.CallingConvention)
{
return false;
}
if (sourceSig.CallingConvention == Cci.CallingConvention.Unmanaged &&
!sourceSig.GetCallingConventionModifiers().SetEqualsWithoutIntermediateHashSet(destinationSig.GetCallingConventionModifiers()))
{
return false;
}
for (int i = 0; i < sourceSig.ParameterCount; i++)
{
var sourceParam = sourceSig.Parameters[i];
var destinationParam = destinationSig.Parameters[i];
if (sourceParam.RefKind != destinationParam.RefKind)
{
return false;
}
if (!hasConversion(sourceParam.RefKind, destinationSig.Parameters[i].TypeWithAnnotations, sourceSig.Parameters[i].TypeWithAnnotations, ref useSiteInfo))
{
return false;
}
}
return sourceSig.RefKind == destinationSig.RefKind
&& hasConversion(sourceSig.RefKind, sourceSig.ReturnTypeWithAnnotations, destinationSig.ReturnTypeWithAnnotations, ref useSiteInfo);
bool hasConversion(RefKind refKind, TypeWithAnnotations sourceType, TypeWithAnnotations destinationType, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
switch (refKind)
{
case RefKind.None:
return (!IncludeNullability || HasTopLevelNullabilityImplicitConversion(sourceType, destinationType))
&& (HasIdentityOrImplicitReferenceConversion(sourceType.Type, destinationType.Type, ref useSiteInfo)
|| HasImplicitPointerToVoidConversion(sourceType.Type, destinationType.Type)
|| HasImplicitPointerConversion(sourceType.Type, destinationType.Type, ref useSiteInfo));
default:
return (!IncludeNullability || HasTopLevelNullabilityIdentityConversion(sourceType, destinationType))
&& HasIdentityConversion(sourceType.Type, destinationType.Type);
}
}
}
#nullable disable
private bool HasIdentityOrReferenceConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
if (HasIdentityConversionInternal(source, destination))
{
return true;
}
if (HasImplicitReferenceConversion(source, destination, ref useSiteInfo))
{
return true;
}
if (HasExplicitReferenceConversion(source, destination, ref useSiteInfo))
{
return true;
}
return false;
}
private bool HasExplicitReferenceConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
// SPEC: The explicit reference conversions are:
// SPEC: From object and dynamic to any other reference type.
if (source.SpecialType == SpecialType.System_Object)
{
if (destination.IsReferenceType)
{
return true;
}
}
else if (source.Kind == SymbolKind.DynamicType && destination.IsReferenceType)
{
return true;
}
// SPEC: From any class-type S to any class-type T, provided S is a base class of T.
if (destination.IsClassType() && IsBaseClass(destination, source, ref useSiteInfo))
{
return true;
}
// SPEC: From any class-type S to any interface-type T, provided S is not sealed and provided S does not implement T.
// ISSUE: class C : IEnumerable<Mammal> { } converting this to IEnumerable<Animal> is not an explicit conversion,
// ISSUE: it is an implicit conversion.
if (source.IsClassType() && destination.IsInterfaceType() && !source.IsSealed && !HasAnyBaseInterfaceConversion(source, destination, ref useSiteInfo))
{
return true;
}
// SPEC: From any interface-type S to any class-type T, provided T is not sealed or provided T implements S.
// ISSUE: What if T is sealed and implements an interface variance-convertible to S?
// ISSUE: eg, sealed class C : IEnum<Mammal> { ... } you should be able to cast an IEnum<Animal> to C.
if (source.IsInterfaceType() && destination.IsClassType() && (!destination.IsSealed || HasAnyBaseInterfaceConversion(destination, source, ref useSiteInfo)))
{
return true;
}
// SPEC: From any interface-type S to any interface-type T, provided S is not derived from T.
// ISSUE: This does not rule out identity conversions, which ought not to be classified as
// ISSUE: explicit reference conversions.
// ISSUE: IEnumerable<Mammal> and IEnumerable<Animal> do not derive from each other but this is
// ISSUE: not an explicit reference conversion, this is an implicit reference conversion.
if (source.IsInterfaceType() && destination.IsInterfaceType() && !HasImplicitConversionToInterface(source, destination, ref useSiteInfo))
{
return true;
}
// SPEC: UNDONE: From a reference type to a reference type T if it has an explicit reference conversion to a reference type T0 and T0 has an identity conversion T.
// SPEC: UNDONE: From a reference type to an interface or delegate type T if it has an explicit reference conversion to an interface or delegate type T0 and either T0 is variance-convertible to T or T is variance-convertible to T0 (§13.1.3.2).
if (HasExplicitArrayConversion(source, destination, ref useSiteInfo))
{
return true;
}
if (HasExplicitDelegateConversion(source, destination, ref useSiteInfo))
{
return true;
}
if (HasExplicitReferenceTypeParameterConversion(source, destination, ref useSiteInfo))
{
return true;
}
return false;
}
// Spec 6.2.7 Explicit conversions involving type parameters
private bool HasExplicitReferenceTypeParameterConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
TypeParameterSymbol s = source as TypeParameterSymbol;
TypeParameterSymbol t = destination as TypeParameterSymbol;
if (s?.AllowsRefLikeType == true || t?.AllowsRefLikeType == true)
{
return false;
}
// SPEC: The following explicit conversions exist for a given type parameter T:
// SPEC: If T is known to be a reference type, the conversions are all classified as explicit reference conversions.
// SPEC: If T is not known to be a reference type, the conversions are classified as unboxing conversions.
// SPEC: From the effective base class C of T to T and from any base class of C to T.
if ((object)t != null && t.IsReferenceType)
{
for (var type = t.EffectiveBaseClass(ref useSiteInfo); (object)type != null; type = type.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteInfo))
{
if (HasIdentityConversionInternal(type, source))
{
return true;
}
}
}
// SPEC: From any interface type to T.
if ((object)t != null && source.IsInterfaceType() && t.IsReferenceType)
{
return true;
}
// SPEC: From T to any interface-type I provided there is not already an implicit conversion from T to I.
if ((object)s != null && s.IsReferenceType && destination.IsInterfaceType() && !HasImplicitReferenceTypeParameterConversion(s, destination, ref useSiteInfo))
{
return true;
}
// SPEC: From a type parameter U to T, provided T depends on U (§10.1.5)
if ((object)s != null && (object)t != null && t.IsReferenceType && t.DependsOn(s))
{
return true;
}
return false;
}
// Spec 6.2.7 Explicit conversions involving type parameters
private bool HasUnboxingTypeParameterConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
TypeParameterSymbol s = source as TypeParameterSymbol;
TypeParameterSymbol t = destination as TypeParameterSymbol;
if (s?.AllowsRefLikeType == true || t?.AllowsRefLikeType == true)
{
return false;
}
// SPEC: The following explicit conversions exist for a given type parameter T:
// SPEC: If T is known to be a reference type, the conversions are all classified as explicit reference conversions.
// SPEC: If T is not known to be a reference type, the conversions are classified as unboxing conversions.
// SPEC: From the effective base class C of T to T and from any base class of C to T.
if ((object)t != null && !t.IsReferenceType)
{
for (var type = t.EffectiveBaseClass(ref useSiteInfo); (object)type != null; type = type.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteInfo))
{
if (TypeSymbol.Equals(type, source, TypeCompareKind.ConsiderEverything2))
{
return true;
}
}
}
// SPEC: From any interface type to T.
if (source.IsInterfaceType() && (object)t != null && !t.IsReferenceType)
{
return true;
}
// SPEC: From T to any interface-type I provided there is not already an implicit conversion from T to I.
if ((object)s != null && !s.IsReferenceType && destination.IsInterfaceType() && !HasImplicitReferenceTypeParameterConversion(s, destination, ref useSiteInfo))
{
return true;
}
// SPEC: From a type parameter U to T, provided T depends on U (§10.1.5)
if ((object)s != null && (object)t != null && !t.IsReferenceType && t.DependsOn(s))
{
return true;
}
return false;
}
private bool HasExplicitDelegateConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
// SPEC: From System.Delegate and the interfaces it implements to any delegate-type.
// We also support System.MulticastDelegate in the implementation, in spite of it not being mentioned in the spec.
if (destination.IsDelegateType())
{
if (source.SpecialType == SpecialType.System_Delegate || source.SpecialType == SpecialType.System_MulticastDelegate)
{
return true;
}
if (HasImplicitConversionToInterface(this.corLibrary.GetDeclaredSpecialType(SpecialType.System_Delegate), source, ref useSiteInfo))
{
return true;
}
}
// SPEC: From D<S1...Sn> to a D<T1...Tn> where D<X1...Xn> is a generic delegate type, D<S1...Sn> is not compatible with or identical to D<T1...Tn>,
// SPEC: and for each type parameter Xi of D the following holds:
// SPEC: If Xi is invariant, then Si is identical to Ti.
// SPEC: If Xi is covariant, then there is an implicit or explicit identity or reference conversion from Si to Ti.
// SPECL If Xi is contravariant, then Si and Ti are either identical or both reference types.
if (!source.IsDelegateType() || !destination.IsDelegateType())
{
return false;
}
if (!TypeSymbol.Equals(source.OriginalDefinition, destination.OriginalDefinition, TypeCompareKind.ConsiderEverything2))
{
return false;
}
var sourceType = (NamedTypeSymbol)source;
var destinationType = (NamedTypeSymbol)destination;
var original = sourceType.OriginalDefinition;
if (HasIdentityConversionInternal(source, destination))
{
return false;
}
if (HasDelegateVarianceConversion(source, destination, ref useSiteInfo))
{
return false;
}
var sourceTypeArguments = sourceType.TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo);
var destinationTypeArguments = destinationType.TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo);
for (int i = 0; i < sourceTypeArguments.Length; ++i)
{
var sourceArg = sourceTypeArguments[i].Type;
var destinationArg = destinationTypeArguments[i].Type;
switch (original.TypeParameters[i].Variance)
{
case VarianceKind.None:
if (!HasIdentityConversionInternal(sourceArg, destinationArg))
{
return false;
}
break;
case VarianceKind.Out:
if (!HasIdentityOrReferenceConversion(sourceArg, destinationArg, ref useSiteInfo))
{
return false;
}
break;
case VarianceKind.In:
bool hasIdentityConversion = HasIdentityConversionInternal(sourceArg, destinationArg);
bool bothAreReferenceTypes = sourceArg.IsReferenceType && destinationArg.IsReferenceType;
if (!(hasIdentityConversion || bothAreReferenceTypes))
{
return false;
}
break;
}
}
return true;
}
private bool HasExplicitArrayConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
var sourceArray = source as ArrayTypeSymbol;
var destinationArray = destination as ArrayTypeSymbol;
// SPEC: From an array-type S with an element type SE to an array-type T with an element type TE, provided all of the following are true:
// SPEC: S and T differ only in element type. (In other words, S and T have the same number of dimensions.)
// SPEC: Both SE and TE are reference-types.
// SPEC: An explicit reference conversion exists from SE to TE.
if ((object)sourceArray != null && (object)destinationArray != null)
{
// HasExplicitReferenceConversion checks that SE and TE are reference types so
// there's no need for that check here. Moreover, it's not as simple as checking
// IsReferenceType, at least not in the case of type parameters, since SE will be
// considered a reference type implicitly in the case of "where TE : class, SE" even
// though SE.IsReferenceType may be false. Again, HasExplicitReferenceConversion
// already handles these cases.
return sourceArray.HasSameShapeAs(destinationArray) &&
HasExplicitReferenceConversion(sourceArray.ElementType, destinationArray.ElementType, ref useSiteInfo);
}
// SPEC: From System.Array and the interfaces it implements to any array-type.
if ((object)destinationArray != null)
{
if (source.SpecialType == SpecialType.System_Array)
{
return true;
}
foreach (var iface in this.corLibrary.GetDeclaredSpecialType(SpecialType.System_Array).AllInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteInfo))
{
if (HasIdentityConversionInternal(iface, source))
{
return true;
}
}
}
// SPEC: From a single-dimensional array type S[] to System.Collections.Generic.IList<T> and its base interfaces
// SPEC: provided that there is an explicit reference conversion from S to T.
// The framework now also allows arrays to be converted to IReadOnlyList<T> and IReadOnlyCollection<T>; we
// honor that as well.
if ((object)sourceArray != null && sourceArray.IsSZArray && destination.IsPossibleArrayGenericInterface())
{
if (HasExplicitReferenceConversion(sourceArray.ElementType, ((NamedTypeSymbol)destination).TypeArgumentWithDefinitionUseSiteDiagnostics(0, ref useSiteInfo).Type, ref useSiteInfo))
{
return true;
}
}
// SPEC: From System.Collections.Generic.IList<S> and its base interfaces to a single-dimensional array type T[],
// provided that there is an explicit identity or reference conversion from S to T.
// Similarly, we honor IReadOnlyList<S> and IReadOnlyCollection<S> in the same way.
if ((object)destinationArray != null && destinationArray.IsSZArray)
{
var specialDefinition = ((TypeSymbol)source.OriginalDefinition).SpecialType;
if (specialDefinition == SpecialType.System_Collections_Generic_IList_T ||
specialDefinition == SpecialType.System_Collections_Generic_ICollection_T ||
specialDefinition == SpecialType.System_Collections_Generic_IEnumerable_T ||
specialDefinition == SpecialType.System_Collections_Generic_IReadOnlyList_T ||
specialDefinition == SpecialType.System_Collections_Generic_IReadOnlyCollection_T)
{
var sourceElement = ((NamedTypeSymbol)source).TypeArgumentWithDefinitionUseSiteDiagnostics(0, ref useSiteInfo).Type;
var destinationElement = destinationArray.ElementType;
if (HasIdentityConversionInternal(sourceElement, destinationElement))
{
return true;
}
if (HasImplicitReferenceConversion(sourceElement, destinationElement, ref useSiteInfo))
{
return true;
}
if (HasExplicitReferenceConversion(sourceElement, destinationElement, ref useSiteInfo))
{
return true;
}
}
}
return false;
}
private bool HasUnboxingConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
if (destination.IsPointerOrFunctionPointer())
{
return false;
}
// Ref-like types cannot be boxed or unboxed
if (destination.IsRestrictedType())
{
return false;
}
// SPEC: An unboxing conversion permits a reference type to be explicitly converted to a value-type.
// SPEC: An unboxing conversion exists from the types object and System.ValueType to any non-nullable-value-type,
var specialTypeSource = source.SpecialType;
if (specialTypeSource == SpecialType.System_Object || specialTypeSource == SpecialType.System_ValueType)
{
if (destination.IsValueType && !destination.IsNullableType())
{
return true;
}
}
// SPEC: and from any interface-type to any non-nullable-value-type that implements the interface-type.
if (source.IsInterfaceType() &&
destination.IsValueType &&
!destination.IsNullableType() &&
HasBoxingConversion(destination, source, ref useSiteInfo))
{
return true;
}
// SPEC: Furthermore type System.Enum can be unboxed to any enum-type.
if (source.SpecialType == SpecialType.System_Enum && destination.IsEnumType())
{
return true;
}
// SPEC: An unboxing conversion exists from a reference type to a nullable-type if an unboxing
// SPEC: conversion exists from the reference type to the underlying non-nullable-value-type
// SPEC: of the nullable-type.
if (source.IsReferenceType &&
destination.IsNullableType() &&
HasUnboxingConversion(source, destination.GetNullableUnderlyingType(), ref useSiteInfo))
{
return true;
}
// SPEC: UNDONE A value type S has an unboxing conversion from an interface type I if it has an unboxing
// SPEC: UNDONE conversion from an interface type I0 and I0 has an identity conversion to I.
// SPEC: UNDONE A value type S has an unboxing conversion from an interface type I if it has an unboxing conversion
// SPEC: UNDONE from an interface or delegate type I0 and either I0 is variance-convertible to I or I is variance-convertible to I0.
if (HasUnboxingTypeParameterConversion(source, destination, ref useSiteInfo))
{
return true;
}
return false;
}
private static bool HasPointerToPointerConversion(TypeSymbol source, TypeSymbol destination)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
return source.IsPointerOrFunctionPointer() && destination.IsPointerOrFunctionPointer();
}
private static bool HasPointerToIntegerConversion(TypeSymbol source, TypeSymbol destination)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
if (!source.IsPointerOrFunctionPointer())
{
return false;
}
// SPEC OMISSION:
//
// The spec should state that any pointer type is convertible to
// sbyte, byte, ... etc, or any corresponding nullable type.
return IsIntegerTypeSupportingPointerConversions(destination.StrippedType());
}
private static bool HasIntegerToPointerConversion(TypeSymbol source, TypeSymbol destination)
{
Debug.Assert((object)source != null);
Debug.Assert((object)destination != null);
if (!destination.IsPointerOrFunctionPointer())
{
return false;
}
// Note that void* is convertible to int?, but int? is not convertible to void*.
return IsIntegerTypeSupportingPointerConversions(source);
}
private static bool IsIntegerTypeSupportingPointerConversions(TypeSymbol type)
{
switch (type.SpecialType)
{
case SpecialType.System_SByte:
case SpecialType.System_Byte:
case SpecialType.System_Int16:
case SpecialType.System_UInt16:
case SpecialType.System_Int32:
case SpecialType.System_UInt32:
case SpecialType.System_Int64:
case SpecialType.System_UInt64:
return true;
case SpecialType.System_IntPtr:
case SpecialType.System_UIntPtr:
return type.IsNativeIntegerType;
}
return false;
}
#nullable enable
private bool IsFeatureFirstClassSpanEnabled
{
get
{
// Note: when Compilation is null, we assume latest LangVersion.
return Compilation?.IsFeatureEnabled(MessageID.IDS_FeatureFirstClassSpan) != false;
}
}
private bool HasImplicitSpanConversion(TypeSymbol? source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
if (source is null || !IsFeatureFirstClassSpanEnabled)
{
return false;
}
// SPEC: From any single-dimensional `array_type` with element type `Ei`...
if (source is ArrayTypeSymbol { IsSZArray: true, ElementTypeWithAnnotations: { } elementType })
{
// SPEC: ...to `System.Span<Ei>`.
if (destination.IsSpan())
{
var spanElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0];
return hasIdentityConversion(elementType, spanElementType);
}
// SPEC: ...to `System.ReadOnlySpan<Ui>`, provided that `Ei` is covariance-convertible to `Ui`.
if (destination.IsReadOnlySpan())
{
var spanElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0];
return hasCovariantConversion(elementType, spanElementType, ref useSiteInfo);
}
}
// SPEC: From `System.Span<Ti>` to `System.ReadOnlySpan<Ui>`, provided that `Ti` is covariance-convertible to `Ui`.
// SPEC: From `System.ReadOnlySpan<Ti>` to `System.ReadOnlySpan<Ui>`, provided that `Ti` is covariance-convertible to `Ui`.
else if (source.IsSpan() || source.IsReadOnlySpan())
{
if (destination.IsReadOnlySpan())
{
var sourceElementType = ((NamedTypeSymbol)source).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0];
var destinationElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0];
return hasCovariantConversion(sourceElementType, destinationElementType, ref useSiteInfo);
}
}
// SPEC: From `string` to `System.ReadOnlySpan<char>`.
else if (source.IsStringType())
{
if (destination.IsReadOnlySpan())
{
var spanElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0];
return spanElementType.SpecialType is SpecialType.System_Char;
}
}
return false;
bool hasCovariantConversion(TypeWithAnnotations source, TypeWithAnnotations destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
return hasIdentityConversion(source, destination) ||
HasImplicitReferenceConversion(source, destination, ref useSiteInfo);
}
bool hasIdentityConversion(TypeWithAnnotations source, TypeWithAnnotations destination)
{
return HasIdentityConversionInternal(source.Type, destination.Type) &&
HasTopLevelNullabilityIdentityConversion(source, destination);
}
}
/// <remarks>
/// This does not check implicit span conversions, that should be done by the caller.
/// </remarks>
private bool HasExplicitSpanConversion(TypeSymbol? source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
if (!IsFeatureFirstClassSpanEnabled)
{
return false;
}
// SPEC: From any single-dimensional `array_type` with element type `Ti`
// to `System.Span<Ui>` or `System.ReadOnlySpan<Ui>`
// provided an explicit reference conversion exists from `Ti` to `Ui`.
if (source is ArrayTypeSymbol { IsSZArray: true, ElementTypeWithAnnotations: { } elementType } &&
(destination.IsSpan() || destination.IsReadOnlySpan()))
{
var spanElementType = ((NamedTypeSymbol)destination).TypeArgumentsWithDefinitionUseSiteDiagnostics(ref useSiteInfo)[0];
return HasIdentityOrReferenceConversion(elementType.Type, spanElementType.Type, ref useSiteInfo) &&
HasTopLevelNullabilityIdentityConversion(elementType, spanElementType);
}
return false;
}
private bool IgnoreUserDefinedSpanConversions(TypeSymbol? source, TypeSymbol? target)
{
// SPEC: User-defined conversions are not considered when converting between types
// for which an implicit or an explicit span conversion exists.
var discarded = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
return source is not null && target is not null &&
(HasImplicitSpanConversion(source, target, ref discarded) ||
HasExplicitSpanConversion(source, target, ref discarded));
}
}
}
|