/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversion.cs
1 329 строк
55 KB
Fred Silberberg
Add C# 15 language version (#84799)
11 авг 2026, 23:51
Не верифицирован
11 авг 2026, 23:51
1284a4a
Код
Авторство
О чём код?
// 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. using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; using System; using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.Operations; namespace Microsoft.CodeAnalysis.CSharp { /// <summary> /// Summarizes whether a conversion is allowed, and if so, which kind of conversion (and in some cases, the /// associated symbol). /// </summary> public readonly struct Conversion : IEquatable<Conversion>, IConvertibleConversion { private readonly ConversionKind _kind; private readonly UncommonData? _uncommonData; // most conversions are trivial and do not require additional data besides Kind // in uncommon cases an instance of this class is attached to the conversion. private abstract class UncommonData { } private sealed class MethodUncommonData : UncommonData { public static readonly MethodUncommonData NoApplicableOperators = new MethodUncommonData( isExtensionMethod: false, isArrayIndex: false, conversionResult: UserDefinedConversionResult.NoApplicableOperators(ImmutableArray<UserDefinedConversionAnalysis>.Empty), conversionMethod: null); public MethodUncommonData( bool isExtensionMethod, bool isArrayIndex, UserDefinedConversionResult conversionResult, MethodSymbol? conversionMethod) { _conversionMethod = conversionMethod; _conversionResult = conversionResult; _flags = isExtensionMethod ? IsExtensionMethodMask : (byte)0; if (isArrayIndex) { _flags |= IsArrayIndexMask; } } internal readonly MethodSymbol? _conversionMethod; //no effect on Equals/GetHashCode internal readonly UserDefinedConversionResult _conversionResult; private const byte IsExtensionMethodMask = 1 << 0; private const byte IsArrayIndexMask = 1 << 1; private readonly byte _flags; internal bool IsExtensionMethod { get { return (_flags & IsExtensionMethodMask) != 0; } } // used by dynamic, which needs to know if a conversion is an array index conversion. internal bool IsArrayIndex { get { return (_flags & IsArrayIndexMask) != 0; } } } private class NestedUncommonData : UncommonData { public NestedUncommonData(ImmutableArray<Conversion> nestedConversions) { _nestedConversionsOpt = nestedConversions; } internal readonly ImmutableArray<Conversion> _nestedConversionsOpt; #if DEBUG internal bool _nestedConversionsChecked; #endif } private sealed class DeconstructionUncommonData : UncommonData { internal DeconstructionUncommonData(DeconstructMethodInfo deconstructMethodInfoOpt, ImmutableArray<(BoundValuePlaceholder? placeholder, BoundExpression? conversion)> deconstructConversionInfo) { Debug.Assert(!deconstructConversionInfo.IsDefaultOrEmpty); DeconstructMethodInfo = deconstructMethodInfoOpt; DeconstructConversionInfo = deconstructConversionInfo; } internal readonly DeconstructMethodInfo DeconstructMethodInfo; internal readonly ImmutableArray<(BoundValuePlaceholder? placeholder, BoundExpression? conversion)> DeconstructConversionInfo; } private sealed class CollectionExpressionUncommonData : NestedUncommonData { internal CollectionExpressionUncommonData( CollectionExpressionTypeKind collectionExpressionTypeKind, TypeSymbol elementType, MethodSymbol? constructor, bool constructorUsedInExpandedForm, ImmutableArray<Conversion> elementConversions) : base(elementConversions) { Debug.Assert(collectionExpressionTypeKind != CollectionExpressionTypeKind.None); Debug.Assert(elementType is { }); CollectionExpressionTypeKind = collectionExpressionTypeKind; ElementType = elementType; Constructor = constructor; ConstructorUsedInExpandedForm = constructorUsedInExpandedForm; } internal readonly CollectionExpressionTypeKind CollectionExpressionTypeKind; internal readonly TypeSymbol ElementType; internal readonly MethodSymbol? Constructor; internal readonly bool ConstructorUsedInExpandedForm; } internal static Conversion CreateCollectionExpressionConversion( CollectionExpressionTypeKind collectionExpressionTypeKind, TypeSymbol elementType, MethodSymbol? constructor, bool constructorUsedInExpandedForm, ImmutableArray<Conversion> elementConversions) { return new Conversion( ConversionKind.CollectionExpression, new CollectionExpressionUncommonData(collectionExpressionTypeKind, elementType, constructor, constructorUsedInExpandedForm, elementConversions)); } internal static Conversion CreateUnionConversion(UserDefinedConversionResult conversionResult) { return new Conversion( ConversionKind.Union, new MethodUncommonData( isExtensionMethod: false, isArrayIndex: false, conversionResult: conversionResult, conversionMethod: null)); } private Conversion( ConversionKind kind, UncommonData? uncommonData = null) { _kind = kind; _uncommonData = uncommonData; Validate(); } [Conditional("DEBUG")] private void Validate() { Debug.Assert(!(this is { IsNullable: true, UnderlyingConversions: var underlying } && (underlying[0].IsUserDefined || underlying[0].IsUnion))); } internal Conversion(UserDefinedConversionResult conversionResult, bool isImplicit) { _kind = conversionResult.Kind == UserDefinedConversionResultKind.NoApplicableOperators ? ConversionKind.NoConversion : isImplicit ? ConversionKind.ImplicitUserDefined : ConversionKind.ExplicitUserDefined; _uncommonData = conversionResult.Kind == UserDefinedConversionResultKind.NoApplicableOperators && conversionResult.Results.IsEmpty ? MethodUncommonData.NoApplicableOperators : new MethodUncommonData( isExtensionMethod: false, isArrayIndex: false, conversionResult: conversionResult, conversionMethod: null); Validate(); } // For the method group, lambda and anonymous method conversions internal Conversion(ConversionKind kind, MethodSymbol conversionMethod, bool isExtensionMethod) { this._kind = kind; _uncommonData = new MethodUncommonData( isExtensionMethod: isExtensionMethod, isArrayIndex: false, conversionResult: default, conversionMethod: conversionMethod); Validate(); } internal Conversion(ConversionKind kind, ImmutableArray<Conversion> nestedConversions) { this._kind = kind; _uncommonData = new NestedUncommonData( nestedConversions: nestedConversions); Validate(); } internal Conversion(ConversionKind kind, DeconstructMethodInfo deconstructMethodInfo, ImmutableArray<(BoundValuePlaceholder? placeholder, BoundExpression? conversion)> deconstructConversionInfo) { Debug.Assert(kind == ConversionKind.Deconstruction); this._kind = kind; _uncommonData = new DeconstructionUncommonData(deconstructMethodInfo, deconstructConversionInfo); Validate(); } internal Conversion SetConversionMethod(MethodSymbol conversionMethod) { // we use this method to patch up the conversion method only in two cases - // 1) when rewriting MethodGroup conversions and the method gets substituted. // 2) when lowering IntPtr conversion (a compat-related conversion which becomes a kind of a user-defined conversion) // 3) when rewriting user-defined conversions and the method gets substituted // in those cases it is ok to ignore existing _uncommonData. Debug.Assert(_kind is ConversionKind.MethodGroup or ConversionKind.IntPtr or ConversionKind.ImplicitUserDefined or ConversionKind.ExplicitUserDefined); return new Conversion(this.Kind, conversionMethod, isExtensionMethod: IsExtensionMethod); } internal Conversion SetArrayIndexConversionForDynamic() { Debug.Assert(_kind.IsDynamic()); Debug.Assert(_uncommonData == null); return new Conversion( _kind, new MethodUncommonData( isExtensionMethod: false, isArrayIndex: true, conversionResult: default, conversionMethod: null)); } [Conditional("DEBUG")] private static void AssertTrivialConversion(ConversionKind kind) { bool isTrivial; switch (kind) { case ConversionKind.NoConversion: case ConversionKind.Identity: case ConversionKind.ImplicitConstant: case ConversionKind.ImplicitNumeric: case ConversionKind.ImplicitReference: case ConversionKind.ImplicitEnumeration: case ConversionKind.ImplicitThrow: case ConversionKind.AnonymousFunction: case ConversionKind.Boxing: case ConversionKind.NullLiteral: case ConversionKind.DefaultLiteral: case ConversionKind.ImplicitNullToPointer: case ConversionKind.ImplicitPointerToVoid: case ConversionKind.ExplicitPointerToPointer: case ConversionKind.ExplicitPointerToInteger: case ConversionKind.ExplicitIntegerToPointer: case ConversionKind.Unboxing: case ConversionKind.ExplicitReference: case ConversionKind.IntPtr: case ConversionKind.ExplicitEnumeration: case ConversionKind.ExplicitNumeric: case ConversionKind.ImplicitDynamic: case ConversionKind.ExplicitDynamic: case ConversionKind.InterpolatedString: case ConversionKind.InterpolatedStringHandler: case ConversionKind.InlineArray: case ConversionKind.ImplicitSpan: case ConversionKind.ExplicitSpan: isTrivial = true; break; default: isTrivial = false; break; } RoslynDebug.Assert(isTrivial, $"this conversion needs additional data: {kind}"); } internal static Conversion GetTrivialConversion(ConversionKind kind) { AssertTrivialConversion(kind); return new Conversion(kind); } internal static Conversion UnsetConversion => new Conversion(ConversionKind.UnsetConversionKind); internal static Conversion NoConversion => new Conversion(ConversionKind.NoConversion); internal static Conversion Identity => new Conversion(ConversionKind.Identity); internal static Conversion ImplicitConstant => new Conversion(ConversionKind.ImplicitConstant); internal static Conversion ImplicitNumeric => new Conversion(ConversionKind.ImplicitNumeric); internal static Conversion ImplicitReference => new Conversion(ConversionKind.ImplicitReference); internal static Conversion ImplicitEnumeration => new Conversion(ConversionKind.ImplicitEnumeration); internal static Conversion ImplicitThrow => new Conversion(ConversionKind.ImplicitThrow); internal static Conversion ObjectCreation => new Conversion(ConversionKind.ObjectCreation); internal static Conversion CollectionExpression => new Conversion(ConversionKind.CollectionExpression); internal static Conversion AnonymousFunction => new Conversion(ConversionKind.AnonymousFunction); internal static Conversion Boxing => new Conversion(ConversionKind.Boxing); internal static Conversion NullLiteral => new Conversion(ConversionKind.NullLiteral); internal static Conversion DefaultLiteral => new Conversion(ConversionKind.DefaultLiteral); internal static Conversion NullToPointer => new Conversion(ConversionKind.ImplicitNullToPointer); internal static Conversion PointerToVoid => new Conversion(ConversionKind.ImplicitPointerToVoid); internal static Conversion PointerToPointer => new Conversion(ConversionKind.ExplicitPointerToPointer); internal static Conversion PointerToInteger => new Conversion(ConversionKind.ExplicitPointerToInteger); internal static Conversion IntegerToPointer => new Conversion(ConversionKind.ExplicitIntegerToPointer); internal static Conversion Unboxing => new Conversion(ConversionKind.Unboxing); internal static Conversion ExplicitReference => new Conversion(ConversionKind.ExplicitReference); internal static Conversion IntPtr => new Conversion(ConversionKind.IntPtr); internal static Conversion ExplicitEnumeration => new Conversion(ConversionKind.ExplicitEnumeration); internal static Conversion ExplicitNumeric => new Conversion(ConversionKind.ExplicitNumeric); internal static Conversion ImplicitDynamic => new Conversion(ConversionKind.ImplicitDynamic); internal static Conversion ExplicitDynamic => new Conversion(ConversionKind.ExplicitDynamic); internal static Conversion InterpolatedString => new Conversion(ConversionKind.InterpolatedString); internal static Conversion InterpolatedStringHandler => new Conversion(ConversionKind.InterpolatedStringHandler); internal static Conversion Deconstruction => new Conversion(ConversionKind.Deconstruction); internal static Conversion PinnedObjectToPointer => new Conversion(ConversionKind.PinnedObjectToPointer); internal static Conversion ImplicitPointer => new Conversion(ConversionKind.ImplicitPointer); internal static Conversion FunctionType => new Conversion(ConversionKind.FunctionType); internal static Conversion InlineArray => new Conversion(ConversionKind.InlineArray); internal static Conversion ImplicitSpan => new Conversion(ConversionKind.ImplicitSpan); internal static Conversion ExplicitSpan => new Conversion(ConversionKind.ExplicitSpan); // trivial conversions that could be underlying in nullable conversion // NOTE: tuple conversions can be underlying as well, but they are not trivial internal static ImmutableArray<Conversion> IdentityUnderlying => ConversionSingletons.IdentityUnderlying; internal static ImmutableArray<Conversion> ImplicitConstantUnderlying => ConversionSingletons.ImplicitConstantUnderlying; internal static ImmutableArray<Conversion> ImplicitNumericUnderlying => ConversionSingletons.ImplicitNumericUnderlying; internal static ImmutableArray<Conversion> ExplicitNumericUnderlying => ConversionSingletons.ExplicitNumericUnderlying; internal static ImmutableArray<Conversion> ExplicitEnumerationUnderlying => ConversionSingletons.ExplicitEnumerationUnderlying; internal static ImmutableArray<Conversion> PointerToIntegerUnderlying => ConversionSingletons.PointerToIntegerUnderlying; // Common combinations to avoid allocations: internal static readonly Conversion ExplicitNullableWithExplicitEnumerationUnderlying = new Conversion(ConversionKind.ExplicitNullable, ExplicitEnumerationUnderlying); internal static readonly Conversion ExplicitNullableWithPointerToIntegerUnderlying = new Conversion(ConversionKind.ExplicitNullable, PointerToIntegerUnderlying); internal static readonly Conversion ExplicitNullableWithIdentityUnderlying = new Conversion(ConversionKind.ExplicitNullable, IdentityUnderlying); internal static readonly Conversion ExplicitNullableWithImplicitNumericUnderlying = new Conversion(ConversionKind.ExplicitNullable, ImplicitNumericUnderlying); internal static readonly Conversion ExplicitNullableWithExplicitNumericUnderlying = new Conversion(ConversionKind.ExplicitNullable, ExplicitNumericUnderlying); internal static readonly Conversion ExplicitNullableWithImplicitConstantUnderlying = new Conversion(ConversionKind.ExplicitNullable, ImplicitConstantUnderlying); internal static readonly Conversion ImplicitNullableWithExplicitEnumerationUnderlying = new Conversion(ConversionKind.ImplicitNullable, ExplicitEnumerationUnderlying); internal static readonly Conversion ImplicitNullableWithPointerToIntegerUnderlying = new Conversion(ConversionKind.ImplicitNullable, PointerToIntegerUnderlying); internal static readonly Conversion ImplicitNullableWithIdentityUnderlying = new Conversion(ConversionKind.ImplicitNullable, IdentityUnderlying); internal static readonly Conversion ImplicitNullableWithImplicitNumericUnderlying = new Conversion(ConversionKind.ImplicitNullable, ImplicitNumericUnderlying); internal static readonly Conversion ImplicitNullableWithExplicitNumericUnderlying = new Conversion(ConversionKind.ImplicitNullable, ExplicitNumericUnderlying); internal static readonly Conversion ImplicitNullableWithImplicitConstantUnderlying = new Conversion(ConversionKind.ImplicitNullable, ImplicitConstantUnderlying); // these static fields are not directly inside the Conversion // because that causes CLR loader failure. private static class ConversionSingletons { internal static ImmutableArray<Conversion> IdentityUnderlying = ImmutableArray.Create(Identity); internal static ImmutableArray<Conversion> ImplicitConstantUnderlying = ImmutableArray.Create(ImplicitConstant); internal static ImmutableArray<Conversion> ImplicitNumericUnderlying = ImmutableArray.Create(ImplicitNumeric); internal static ImmutableArray<Conversion> ExplicitNumericUnderlying = ImmutableArray.Create(ExplicitNumeric); internal static ImmutableArray<Conversion> ExplicitEnumerationUnderlying = ImmutableArray.Create(ExplicitEnumeration); internal static ImmutableArray<Conversion> PointerToIntegerUnderlying = ImmutableArray.Create(PointerToInteger); } internal static Conversion MakeStackAllocToPointerType(Conversion underlyingConversion) { return new Conversion(ConversionKind.StackAllocToPointerType, ImmutableArray.Create(underlyingConversion)); } internal static Conversion MakeStackAllocToSpanType(Conversion underlyingConversion) { return new Conversion(ConversionKind.StackAllocToSpanType, ImmutableArray.Create(underlyingConversion)); } internal static Conversion MakeNullableConversion(ConversionKind kind, Conversion nestedConversion) { Debug.Assert(kind == ConversionKind.ImplicitNullable || kind == ConversionKind.ExplicitNullable); return nestedConversion.Kind switch { ConversionKind.Identity => kind == ConversionKind.ImplicitNullable ? ImplicitNullableWithIdentityUnderlying : ExplicitNullableWithIdentityUnderlying, ConversionKind.ImplicitConstant => kind == ConversionKind.ImplicitNullable ? ImplicitNullableWithImplicitConstantUnderlying : ExplicitNullableWithImplicitConstantUnderlying, ConversionKind.ImplicitNumeric => kind == ConversionKind.ImplicitNullable ? ImplicitNullableWithImplicitNumericUnderlying : ExplicitNullableWithImplicitNumericUnderlying, ConversionKind.ExplicitNumeric => kind == ConversionKind.ImplicitNullable ? ImplicitNullableWithExplicitNumericUnderlying : ExplicitNullableWithExplicitNumericUnderlying, ConversionKind.ExplicitEnumeration => kind == ConversionKind.ImplicitNullable ? ImplicitNullableWithExplicitEnumerationUnderlying : ExplicitNullableWithExplicitEnumerationUnderlying, ConversionKind.ExplicitPointerToInteger => kind == ConversionKind.ImplicitNullable ? ImplicitNullableWithPointerToIntegerUnderlying : ExplicitNullableWithPointerToIntegerUnderlying, _ => new Conversion(kind, ImmutableArray.Create(nestedConversion)), }; } internal static Conversion MakeSwitchExpression(ImmutableArray<Conversion> innerConversions) { return new Conversion(ConversionKind.SwitchExpression, innerConversions); } internal static Conversion MakeConditionalExpression(ImmutableArray<Conversion> innerConversions) { return new Conversion(ConversionKind.ConditionalExpression, innerConversions); } internal ConversionKind Kind { get { return _kind; } } internal bool IsExtensionMethod { get { return _uncommonData is MethodUncommonData { IsExtensionMethod: true }; } } internal bool IsArrayIndex { get { return _uncommonData is MethodUncommonData { IsArrayIndex: true }; } } internal ImmutableArray<Conversion> UnderlyingConversions { get { return _uncommonData is NestedUncommonData { _nestedConversionsOpt: var conversions } ? conversions : default(ImmutableArray<Conversion>); } } [Conditional("DEBUG")] internal void AssertUnderlyingConversionsChecked() { #if DEBUG Debug.Assert((_uncommonData as NestedUncommonData)?._nestedConversionsChecked ?? true); #endif } [Conditional("DEBUG")] internal void AssertUnderlyingConversionsCheckedRecursive() { AssertUnderlyingConversionsChecked(); var underlyingConversions = UnderlyingConversions; if (!underlyingConversions.IsDefaultOrEmpty) { foreach (var underlying in underlyingConversions) { underlying.AssertUnderlyingConversionsCheckedRecursive(); } } if (IsUserDefined) { UserDefinedFromConversion.AssertUnderlyingConversionsCheckedRecursive(); UserDefinedToConversion.AssertUnderlyingConversionsCheckedRecursive(); } else if (IsUnion && BestUnionConversionAnalysis is { } analysis) { analysis.SourceConversion.AssertUnderlyingConversionsCheckedRecursive(); analysis.TargetConversion.AssertUnderlyingConversionsCheckedRecursive(); } } [Conditional("DEBUG")] internal void MarkUnderlyingConversionsChecked() { #if DEBUG if (_uncommonData is NestedUncommonData nestedUncommonData) { nestedUncommonData._nestedConversionsChecked = true; } #endif } [Conditional("DEBUG")] internal void MarkUnderlyingConversionsCheckedRecursive() { #if DEBUG if (_uncommonData is not null) { if (_uncommonData is NestedUncommonData nestedUncommonData) { nestedUncommonData._nestedConversionsChecked = true; } var underlyingConversions = UnderlyingConversions; if (!underlyingConversions.IsDefaultOrEmpty) { foreach (var underlying in underlyingConversions) { underlying.MarkUnderlyingConversionsCheckedRecursive(); } } if (IsUserDefined) { UserDefinedFromConversion.MarkUnderlyingConversionsCheckedRecursive(); UserDefinedToConversion.MarkUnderlyingConversionsCheckedRecursive(); } else if (IsUnion && BestUnionConversionAnalysis is { } analysis) { analysis.SourceConversion.MarkUnderlyingConversionsCheckedRecursive(); analysis.TargetConversion.MarkUnderlyingConversionsCheckedRecursive(); } } #endif } internal MethodSymbol? Method { get { switch (_uncommonData) { case MethodUncommonData methodUncommonData: if (methodUncommonData._conversionMethod is { }) { return methodUncommonData._conversionMethod; } var conversionResult = methodUncommonData._conversionResult; if (conversionResult.Kind == UserDefinedConversionResultKind.Valid) { UserDefinedConversionAnalysis analysis = conversionResult.Results[conversionResult.Best]; return analysis.Operator; } break; case DeconstructionUncommonData deconstructionUncommonData: if (deconstructionUncommonData.DeconstructMethodInfo.Invocation is BoundCall call) { return call.Method; } break; } return null; } } internal TypeParameterSymbol? ConstrainedToTypeOpt { get { if (_uncommonData is MethodUncommonData { _conversionMethod: null } methodUncommonData) { var conversionResult = methodUncommonData._conversionResult; if (conversionResult.Kind == UserDefinedConversionResultKind.Valid) { UserDefinedConversionAnalysis analysis = conversionResult.Results[conversionResult.Best]; return analysis.ConstrainedToTypeOpt; } } return null; } } internal DeconstructMethodInfo DeconstructionInfo { get { var uncommonData = (DeconstructionUncommonData?)_uncommonData; return uncommonData == null ? default : uncommonData.DeconstructMethodInfo; } } internal ImmutableArray<(BoundValuePlaceholder? placeholder, BoundExpression? conversion)> DeconstructConversionInfo { get { var uncommonData = (DeconstructionUncommonData?)_uncommonData; return uncommonData == null ? default : uncommonData.DeconstructConversionInfo; } } internal CollectionExpressionTypeKind GetCollectionExpressionTypeKind(out TypeSymbol? elementType, out MethodSymbol? constructor, out bool isExpanded) { if (_uncommonData is CollectionExpressionUncommonData collectionExpressionData) { elementType = collectionExpressionData.ElementType; constructor = collectionExpressionData.Constructor; isExpanded = collectionExpressionData.ConstructorUsedInExpandedForm; return collectionExpressionData.CollectionExpressionTypeKind; } elementType = null; constructor = null; isExpanded = false; return CollectionExpressionTypeKind.None; } // CONSIDER: public? internal bool IsValid { get { if (!this.Exists) { return false; } if (_uncommonData is NestedUncommonData { _nestedConversionsOpt: { IsDefault: false } nestedConversions }) { foreach (var conv in nestedConversions) { if (!conv.IsValid) { return false; } } Debug.Assert(!this.IsUserDefined); Debug.Assert(!this.IsUnion); return true; } return (!this.IsUserDefined && !this.IsUnion) || this.Method is object || (_uncommonData as MethodUncommonData)?._conversionResult.Kind == UserDefinedConversionResultKind.Valid; } } /// <summary> /// Returns true if the conversion exists, either as an implicit or explicit conversion. /// </summary> /// <remarks> /// The existence of a conversion does not necessarily imply that the conversion is valid. /// For example, an ambiguous user-defined conversion may exist but may not be valid. /// </remarks> public bool Exists { get { return Kind != ConversionKind.NoConversion; } } /// <summary> /// Returns true if the conversion is implicit. /// </summary> /// <remarks> /// Implicit conversions are described in section 6.1 of the C# language specification. /// </remarks> public bool IsImplicit { get { return Kind.IsImplicitConversion(); } } /// <summary> /// Returns true if the conversion is explicit. /// </summary> /// <remarks> /// Explicit conversions are described in section 6.2 of the C# language specification. /// </remarks> public bool IsExplicit { get { // All conversions are either implicit or explicit. return Exists && !IsImplicit; } } /// <summary> /// Returns true if the conversion is an identity conversion. /// </summary> /// <remarks> /// Identity conversions are described in section 6.1.1 of the C# language specification. /// </remarks> public bool IsIdentity { get { return Kind == ConversionKind.Identity; } } /// <summary> /// Returns true if the conversion is a stackalloc conversion. /// </summary> public bool IsStackAlloc { get { return Kind == ConversionKind.StackAllocToPointerType || Kind == ConversionKind.StackAllocToSpanType; } } /// <summary> /// Returns true if the conversion is an implicit numeric conversion or explicit numeric conversion. /// </summary> /// <remarks> /// Implicit and explicit numeric conversions are described in sections 6.1.2 and 6.2.1 of the C# language specification. /// </remarks> public bool IsNumeric { get { return Kind == ConversionKind.ImplicitNumeric || Kind == ConversionKind.ExplicitNumeric; } } /// <summary> /// Returns true if the conversion is an implicit enumeration conversion or explicit enumeration conversion. /// </summary> /// <remarks> /// Implicit and explicit enumeration conversions are described in sections 6.1.3 and 6.2.2 of the C# language specification. /// </remarks> public bool IsEnumeration { get { return Kind == ConversionKind.ImplicitEnumeration || Kind == ConversionKind.ExplicitEnumeration; } } /// <summary> /// Returns true if the conversion is an implicit throw conversion. /// </summary> public bool IsThrow { get { return Kind == ConversionKind.ImplicitThrow; } } /// <summary> /// Returns true if the conversion is an implicit object creation expression conversion. /// </summary> public bool IsObjectCreation { get { return Kind == ConversionKind.ObjectCreation; } } /// <summary> /// Returns true if the conversion is an implicit collection expression conversion. /// </summary> public bool IsCollectionExpression => Kind == ConversionKind.CollectionExpression; /// <summary> /// Returns true if the conversion is an implicit switch expression conversion. /// </summary> public bool IsSwitchExpression { get { return Kind == ConversionKind.SwitchExpression; } } /// <summary> /// Returns true if the conversion is an implicit conditional expression conversion. /// </summary> public bool IsConditionalExpression { get { return Kind == ConversionKind.ConditionalExpression; } } // TODO: update the language reference section number below. /// <summary> /// Returns true if the conversion is an interpolated string conversion. /// </summary> /// <remarks> /// The interpolated string conversion described in section 6.1.N of the C# language specification. /// </remarks> public bool IsInterpolatedString { get { return Kind == ConversionKind.InterpolatedString; } } /// <summary> /// Returns true if the conversion is an interpolated string builder conversion. /// </summary> public bool IsInterpolatedStringHandler { get { return Kind == ConversionKind.InterpolatedStringHandler; } } /// <summary> /// Returns true if the conversion is an inline array conversion. /// </summary> public bool IsInlineArray { get { return Kind == ConversionKind.InlineArray; } } /// <summary> /// Returns true if the conversion is an implicit nullable conversion or explicit nullable conversion. /// </summary> /// <remarks> /// Implicit and explicit nullable conversions are described in sections 6.1.4 and 6.2.3 of the C# language specification. /// </remarks> public bool IsNullable { get { return Kind == ConversionKind.ImplicitNullable || Kind == ConversionKind.ExplicitNullable; } } /// <summary> /// Returns true if the conversion is an implicit tuple literal conversion or explicit tuple literal conversion. /// </summary> public bool IsTupleLiteralConversion { get { return Kind == ConversionKind.ImplicitTupleLiteral || Kind == ConversionKind.ExplicitTupleLiteral; } } /// <summary> /// Returns true if the conversion is an implicit tuple conversion or explicit tuple conversion. /// </summary> public bool IsTupleConversion { get { return Kind == ConversionKind.ImplicitTuple || Kind == ConversionKind.ExplicitTuple; } } /// <summary> /// Returns true if the conversion is an implicit reference conversion or explicit reference conversion. /// </summary> /// <remarks> /// Implicit and explicit reference conversions are described in sections 6.1.6 and 6.2.4 of the C# language specification. /// </remarks> public bool IsReference { get { return Kind == ConversionKind.ImplicitReference || Kind == ConversionKind.ExplicitReference; } } /// <summary> /// Returns true if the conversion is a span conversion. /// </summary> /// <remarks> /// Span conversion is available since C# 13 as part of the "first-class Span types" feature. /// </remarks> public bool IsSpan { get { return Kind is ConversionKind.ImplicitSpan or ConversionKind.ExplicitSpan; } } /// <summary> /// Returns true if the conversion is an implicit user-defined conversion or explicit user-defined conversion. /// </summary> /// <remarks> /// Implicit and explicit user-defined conversions are described in section 6.4 of the C# language specification. /// </remarks> public bool IsUserDefined { get { return Kind.IsUserDefinedConversion(); } } /// <summary> /// Returns true if the conversion is an implicit union conversion. /// </summary> public bool IsUnion { get { return Kind.IsUnionConversion(); } } /// <summary> /// Returns true if the conversion is an implicit boxing conversion. /// </summary> /// <remarks> /// Implicit boxing conversions are described in section 6.1.7 of the C# language specification. /// </remarks> public bool IsBoxing { get { return Kind == ConversionKind.Boxing; } } /// <summary> /// Returns true if the conversion is an explicit unboxing conversion. /// </summary> /// <remarks> /// Explicit unboxing conversions as described in section 6.2.5 of the C# language specification. /// </remarks> public bool IsUnboxing { get { return Kind == ConversionKind.Unboxing; } } /// <summary> /// Returns true if the conversion is an implicit null literal conversion. /// </summary> /// <remarks> /// Null literal conversions are described in section 6.1.5 of the C# language specification. /// </remarks> public bool IsNullLiteral { get { return Kind == ConversionKind.NullLiteral; } } /// <summary> /// Returns true if the conversion is an implicit default literal conversion. /// </summary> public bool IsDefaultLiteral { get { return Kind == ConversionKind.DefaultLiteral; } } /// <summary> /// Returns true if the conversion is an implicit dynamic conversion. /// </summary> /// <remarks> /// Implicit dynamic conversions are described in section 6.1.8 of the C# language specification. /// </remarks> public bool IsDynamic { get { return Kind.IsDynamic(); } } /// <summary> /// Returns true if the conversion is an implicit constant expression conversion. /// </summary> /// <remarks> /// Implicit constant expression conversions are described in section 6.1.9 of the C# language specification. /// </remarks> public bool IsConstantExpression { get { return Kind == ConversionKind.ImplicitConstant; } } /// <summary> /// Returns true if the conversion is an implicit anonymous function conversion. /// </summary> /// <remarks> /// Implicit anonymous function conversions are described in section 6.5 of the C# language specification. /// </remarks> public bool IsAnonymousFunction { get { return Kind == ConversionKind.AnonymousFunction; } } /// <summary> /// Returns true if the conversion is an implicit method group conversion. /// </summary> /// <remarks> /// Implicit method group conversions are described in section 6.6 of the C# language specification. /// </remarks> public bool IsMethodGroup { get { return Kind == ConversionKind.MethodGroup; } } /// <summary> /// Returns true if the conversion is a pointer conversion /// </summary> /// <remarks> /// Pointer conversions are described in section 18.4 of the C# language specification. /// /// Returns true if the conversion is a conversion /// a) from a pointer type to void*, /// b) from a pointer type to another pointer type (other than void*), /// c) from the null literal to a pointer type, /// d) from an integral numeric type to a pointer type, /// e) from a pointer type to an integral numeric type, or /// d) from a function pointer type to a function pointer type. /// /// Does not return true for user-defined conversions to/from pointer types. /// Does not return true for conversions between pointer types and IntPtr/UIntPtr. /// </remarks> public bool IsPointer { get { return this.Kind.IsPointerConversion(); } } /// <summary> /// Returns true if the conversion is a conversion to or from IntPtr or UIntPtr. /// </summary> /// <remarks> /// Returns true if the conversion is a conversion to or from IntPtr or UIntPtr. /// This includes: /// IntPtr to/from int /// IntPtr to/from long /// IntPtr to/from void* /// UIntPtr to/from int /// UIntPtr to/from long /// UIntPtr to/from void* /// </remarks> public bool IsIntPtr { get { return Kind == ConversionKind.IntPtr; } } /// <summary> /// Returns the method used to create the delegate for a method group conversion if <see cref="IsMethodGroup"/> is true /// or the method used to perform the conversion for a user-defined conversion if <see cref="IsUserDefined"/> is true. /// or the method used to perform the conversion for a union conversion if <see cref="IsUnion"/> is true. /// Otherwise, returns null. /// </summary> /// <remarks> /// Method group conversions are described in section 6.6 of the C# language specification. /// User-defined conversions are described in section 6.4 of the C# language specification. /// </remarks> public IMethodSymbol? MethodSymbol { get { return this.Method.GetPublicSymbol(); } } /// <summary> /// Type parameter which runtime type will be used to resolve virtual invocation of the <see cref="MethodSymbol" />, if any. /// Null if <see cref="MethodSymbol" /> is resolved statically, or is null. /// </summary> public ITypeSymbol? ConstrainedToType { get { return this.ConstrainedToTypeOpt.GetPublicSymbol(); } } /// <summary> /// Gives an indication of how successful the conversion was. /// Viable - found a best built-in or user-defined conversion. /// Empty - found no applicable built-in or user-defined conversions. /// OverloadResolutionFailure - found applicable conversions, but no unique best. /// </summary> internal LookupResultKind ResultKind { get { var conversionResult = (_uncommonData as MethodUncommonData)?._conversionResult ?? default(UserDefinedConversionResult); switch (conversionResult.Kind) { case UserDefinedConversionResultKind.Valid: return LookupResultKind.Viable; case UserDefinedConversionResultKind.Ambiguous: case UserDefinedConversionResultKind.NoBestSourceType: case UserDefinedConversionResultKind.NoBestTargetType: return LookupResultKind.OverloadResolutionFailure; case UserDefinedConversionResultKind.NoApplicableOperators: if (conversionResult.Results.IsDefaultOrEmpty) { return this.Kind == ConversionKind.NoConversion ? LookupResultKind.Empty : LookupResultKind.Viable; } else { // CONSIDER: indicating an overload resolution failure is sufficient, // but it would be nice to indicate lack of accessibility or other // error conditions. return LookupResultKind.OverloadResolutionFailure; } default: throw ExceptionUtilities.UnexpectedValue(conversionResult.Kind); } } } /// <summary> /// Conversion applied to operand of the user-defined conversion. /// </summary> internal Conversion UserDefinedFromConversion { get { UserDefinedConversionAnalysis? best = BestUserDefinedConversionAnalysis; return best == null ? Conversion.NoConversion : best.SourceConversion; } } /// <summary> /// Conversion applied to the result of the user-defined conversion. /// </summary> internal Conversion UserDefinedToConversion { get { UserDefinedConversionAnalysis? best = BestUserDefinedConversionAnalysis; return best == null ? Conversion.NoConversion : best.TargetConversion; } } /// <summary> /// The user-defined operators that were considered when attempting this conversion /// (i.e. the arguments to overload resolution). /// </summary> internal ImmutableArray<MethodSymbol> OriginalUserDefinedOrUnionConversions { get { // If overload resolution has failed then we want to stash away the original methods that we // considered so that the IDE can display tooltips or other information about them. // However, if a method group contained a generic method that was type inferred then // the IDE wants information about the *inferred* method, not the original unconstructed // generic method. if (_uncommonData is MethodUncommonData { _conversionResult: { Kind: not UserDefinedConversionResultKind.NoApplicableOperators } conversionResult }) { var builder = ArrayBuilder<MethodSymbol>.GetInstance(); foreach (var analysis in conversionResult.Results) { builder.Add(analysis.Operator); } return builder.ToImmutableAndFree(); } return ImmutableArray<MethodSymbol>.Empty; } } internal UserDefinedConversionAnalysis? BestUserDefinedConversionAnalysis { get { if (!IsUnion && _uncommonData is MethodUncommonData { _conversionResult: { Kind: UserDefinedConversionResultKind.Valid } conversionResult }) { UserDefinedConversionAnalysis analysis = conversionResult.Results[conversionResult.Best]; return analysis; } return null; } } internal UserDefinedConversionAnalysis? BestUnionConversionAnalysis { get { if (IsUnion && _uncommonData is MethodUncommonData { _conversionResult: { Kind: UserDefinedConversionResultKind.Valid } conversionResult }) { UserDefinedConversionAnalysis analysis = conversionResult.Results[conversionResult.Best]; return analysis; } return null; } } /// <summary> /// Creates a <see cref="CommonConversion"/> from this C# conversion. /// </summary> /// <returns>The <see cref="CommonConversion"/> that represents this conversion.</returns> /// <remarks> /// This is a lossy conversion; it is not possible to recover the original <see cref="Conversion"/> /// from the <see cref="CommonConversion"/> struct. /// </remarks> public CommonConversion ToCommonConversion() { // The MethodSymbol of CommonConversion only refers to UserDefined conversions, not method groups var (methodSymbol, constrainedToType) = IsUserDefined || IsUnion ? (MethodSymbol, ConstrainedToType) : (null, null); return new CommonConversion(Exists, IsIdentity, IsNumeric, IsReference, IsImplicit, IsNullable, methodSymbol, constrainedToType); } /// <summary> /// Returns a string that represents the <see cref="Kind"/> of the conversion. /// </summary> /// <returns>A string that represents the <see cref="Kind"/> of the conversion.</returns> public override string ToString() { return this.Kind.ToString(); } /// <summary> /// Determines whether the specified <see cref="Conversion"/> object is equal to the current <see cref="Conversion"/> object. /// </summary> /// <param name="obj">The <see cref="Conversion"/> object to compare with the current <see cref="Conversion"/> object.</param> /// <returns>true if the specified <see cref="Conversion"/> object is equal to the current <see cref="Conversion"/> object; otherwise, false.</returns> public override bool Equals(object? obj) { return obj is Conversion && this.Equals((Conversion)obj); } /// <summary> /// Determines whether the specified <see cref="Conversion"/> object is equal to the current <see cref="Conversion"/> object. /// </summary> /// <param name="other">The <see cref="Conversion"/> object to compare with the current <see cref="Conversion"/> object.</param> /// <returns>true if the specified <see cref="Conversion"/> object is equal to the current <see cref="Conversion"/> object; otherwise, false.</returns> public bool Equals(Conversion other) { return this.Kind == other.Kind && Equals(this._uncommonData, other._uncommonData); } /// <summary> /// Returns a hash code for the current <see cref="Conversion"/> object. /// </summary> /// <returns>A hash code for the current <see cref="Conversion"/> object.</returns> public override int GetHashCode() { return Hash.Combine(this._uncommonData, (int)this.Kind); } /// <summary> /// Returns true if the specified <see cref="Conversion"/> objects are equal and false otherwise. /// </summary> /// <param name="left">The first <see cref="Conversion"/> object.</param> /// <param name="right">The second <see cref="Conversion"/> object.</param> /// <returns></returns> public static bool operator ==(Conversion left, Conversion right) { return left.Equals(right); } /// <summary> /// Returns false if the specified <see cref="Conversion"/> objects are equal and true otherwise. /// </summary> /// <param name="left">The first <see cref="Conversion"/> object.</param> /// <param name="right">The second <see cref="Conversion"/> object.</param> /// <returns></returns> public static bool operator !=(Conversion left, Conversion right) { return !(left == right); } #if DEBUG internal string Dump() { return TreeDumper.DumpCompact(Dump(this)); TreeDumperNode Dump(Conversion self) { var sub = new System.Collections.Generic.List<TreeDumperNode>(); if (self.Method is object) { sub.Add(new TreeDumperNode("method", self.Method.ToDisplayString(), null)); } if (!self.DeconstructionInfo.IsDefault) { sub.Add(new TreeDumperNode("deconstructionInfo", null, new[] { BoundTreeDumperNodeProducer.MakeTree(self.DeconstructionInfo.Invocation) })); } var underlyingConversions = self.UnderlyingConversions; if (!underlyingConversions.IsDefaultOrEmpty) { sub.Add(new TreeDumperNode($"underlyingConversions[{underlyingConversions.Length}]", null, underlyingConversions.SelectAsArray(c => Dump(c)))); } return new TreeDumperNode("conversion", self.Kind, sub); } } #endif } /// <summary>Stores all the information from binding for calling a Deconstruct method.</summary> internal readonly struct DeconstructMethodInfo { internal DeconstructMethodInfo(BoundExpression invocation, BoundDeconstructValuePlaceholder inputPlaceholder, ImmutableArray<BoundDeconstructValuePlaceholder> outputPlaceholders) { Debug.Assert(invocation is not BoundCall { Expanded: true }); (Invocation, InputPlaceholder, OutputPlaceholders) = (invocation, inputPlaceholder, outputPlaceholders); } internal readonly BoundExpression Invocation; internal readonly BoundDeconstructValuePlaceholder InputPlaceholder; internal readonly ImmutableArray<BoundDeconstructValuePlaceholder> OutputPlaceholders; internal bool IsDefault => Invocation is null; } }