File: Lowering\LocalRewriter\LocalRewriter_Conversion.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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 System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal partial class LocalRewriter
    {
        public override BoundNode VisitConversion(BoundConversion node)
        {
            switch (node.ConversionKind)
            {
                case ConversionKind.InterpolatedString:
                    return RewriteInterpolatedStringConversion(node);
                case ConversionKind.InterpolatedStringHandler:
                    Debug.Assert(node.Type is NamedTypeSymbol { IsInterpolatedStringHandlerType: true });
 
                    var (data, parts) = node.Operand switch
                    {
                        BoundInterpolatedString { InterpolationData: { } d, Parts: { } p } => (d, p),
                        BoundBinaryOperator { InterpolatedStringHandlerData: { } d } binary => (d, CollectBinaryOperatorInterpolatedStringParts(binary)),
                        _ => throw ExceptionUtilities.UnexpectedValue(node.Operand.Kind)
                    };
 
                    InterpolationHandlerResult interpolationResult = RewriteToInterpolatedStringHandlerPattern(data, parts, node.Operand.Syntax);
                    return interpolationResult.WithFinalResult(interpolationResult.HandlerTemp);
 
                case ConversionKind.SwitchExpression:
                    // Skip through target-typed switches
                    Debug.Assert(node.Operand is BoundConvertedSwitchExpression { WasTargetTyped: true });
                    return Visit(node.Operand)!;
                case ConversionKind.ConditionalExpression:
                    // Skip through target-typed conditionals
                    Debug.Assert(node.Operand is BoundConditionalOperator { WasTargetTyped: true });
                    return Visit(node.Operand)!;
                case ConversionKind.ObjectCreation:
                    // Skip through target-typed new
                    Debug.Assert(node.Operand is not null);
                    var objectCreation = VisitExpression(node.Operand);
 
                    if (node.Type.IsNullableType())
                    {
                        Debug.Assert(node.Operand is BoundObjectCreationExpressionBase { WasTargetTyped: true });
                        return ConvertToNullable(node.Syntax, node.Type, objectCreation);
                    }
 
                    Debug.Assert(node.Operand is BoundObjectCreationExpressionBase { WasTargetTyped: true } or
                                                 BoundDelegateCreationExpression { WasTargetTyped: true });
 
                    return objectCreation;
 
                case ConversionKind.ImplicitNullable when node.Conversion.UnderlyingConversions[0].Kind is ConversionKind.CollectionExpression:
                    var rewrittenCollection = RewriteCollectionExpressionConversion(node.Conversion.UnderlyingConversions[0], (BoundCollectionExpression)node.Operand);
                    return ConvertToNullable(node.Syntax, node.Type, rewrittenCollection);
 
                case ConversionKind.CollectionExpression:
                    return RewriteCollectionExpressionConversion(node.Conversion, (BoundCollectionExpression)node.Operand);
            }
 
            var rewrittenType = VisitType(node.Type);
 
            bool wasInExpressionLambda = _inExpressionLambda;
            _inExpressionLambda = _inExpressionLambda || (node.ConversionKind == ConversionKind.AnonymousFunction && !wasInExpressionLambda && rewrittenType.IsExpressionTree());
            InstrumentationState.IsSuppressed = _inExpressionLambda;
 
            var rewrittenOperand = VisitExpression(node.Operand);
            _inExpressionLambda = wasInExpressionLambda;
            InstrumentationState.IsSuppressed = _inExpressionLambda;
 
            var result = MakeConversionNode(node, node.Syntax, rewrittenOperand, node.Conversion, node.Checked, node.ExplicitCastInCode, node.ConstantValueOpt, rewrittenType);
 
            var toType = node.Type;
            Debug.Assert(result.Type!.Equals(toType, TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes));
 
            return result;
        }
 
        public override BoundNode VisitUtf8String(BoundUtf8String node)
        {
            return MakeUtf8Span(node, GetUtf8ByteRepresentation(node));
        }
 
        private BoundExpression MakeUtf8Span(BoundExpression node, IReadOnlyList<byte>? bytes)
        {
            Debug.Assert(node.Type is not null);
            Debug.Assert(_compilation.IsReadOnlySpanType(node.Type));
            var byteType = ((NamedTypeSymbol)node.Type).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Single().Type;
            Debug.Assert(byteType.SpecialType == SpecialType.System_Byte);
 
            var save_Syntax = _factory.Syntax;
            _factory.Syntax = node.Syntax;
 
            int length = 0;
            BoundExpression result;
            var byteArray = ArrayTypeSymbol.CreateSZArray(_compilation.Assembly, TypeWithAnnotations.Create(byteType));
            BoundExpression utf8Bytes = bytes is null ?
                                            BadExpression(node.Syntax, byteArray, ImmutableArray<BoundExpression>.Empty) :
                                            MakeUnderlyingArrayForUtf8Span(node.Syntax, byteArray, bytes, out length);
 
            if (!TryGetWellKnownTypeMember<MethodSymbol>(node.Syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Array_Start_Length, out MethodSymbol? ctor))
            {
                result = BadExpression(node.Syntax, node.Type, ImmutableArray<BoundExpression>.Empty);
            }
            else
            {
                result = new BoundObjectCreationExpression(node.Syntax, ctor.AsMember((NamedTypeSymbol)node.Type), utf8Bytes, _factory.Literal(0), _factory.Literal(length));
            }
 
            _factory.Syntax = save_Syntax;
 
            return result;
        }
 
        private byte[]? GetUtf8ByteRepresentation(BoundUtf8String node)
        {
            var utf8 = new System.Text.UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
 
            try
            {
                return utf8.GetBytes(node.Value);
            }
            catch (Exception ex)
            {
                _diagnostics.Add(
                    ErrorCode.ERR_CannotBeConvertedToUtf8,
                    node.Syntax.Location,
                    ex.Message);
 
                return null;
            }
        }
 
        private BoundArrayCreation MakeUnderlyingArrayForUtf8Span(SyntaxNode syntax, ArrayTypeSymbol byteArray, IReadOnlyList<byte> bytes, out int length)
        {
            Debug.Assert(byteArray.IsSZArray);
            Debug.Assert(byteArray.ElementType.SpecialType == SpecialType.System_Byte);
 
            var builder = ArrayBuilder<BoundExpression>.GetInstance(bytes.Count + 1);
            foreach (byte b in bytes)
            {
                builder.Add(_factory.Literal(b));
            }
 
            length = builder.Count;
 
            // Zero terminate memory
            builder.Add(_factory.Literal((byte)0));
 
            var utf8Bytes = new BoundArrayCreation(
                                    syntax,
                                    ImmutableArray.Create<BoundExpression>(_factory.Literal(builder.Count)),
                                    new BoundArrayInitialization(syntax, isInferred: false, builder.ToImmutableAndFree()),
                                    byteArray);
            return utf8Bytes;
        }
 
        private BoundExpression VisitUtf8Addition(BoundBinaryOperator node)
        {
            Debug.Assert(node.OperatorKind is BinaryOperatorKind.Utf8Addition);
 
            var bytesBuilder = ArrayBuilder<byte>.GetInstance();
            bool haveRepresentationError = false;
            var stack = ArrayBuilder<BoundExpression>.GetInstance();
 
            stack.Add(node);
 
            while (stack.Count != 0)
            {
                var current = stack.Pop();
 
                switch (current)
                {
                    case BoundUtf8String literal:
                        byte[]? bytes = GetUtf8ByteRepresentation(literal);
 
                        if (bytes is null)
                        {
                            haveRepresentationError = true;
                        }
                        else if (!haveRepresentationError)
                        {
                            bytesBuilder.AddRange(bytes);
                        }
                        break;
 
                    case BoundBinaryOperator utf8Addition:
                        Debug.Assert(utf8Addition.OperatorKind is BinaryOperatorKind.Utf8Addition);
                        stack.Push(utf8Addition.Right);
                        stack.Push(utf8Addition.Left);
                        break;
 
                    default:
                        throw ExceptionUtilities.UnexpectedValue(current);
                }
            }
 
            stack.Free();
 
            BoundExpression result = MakeUtf8Span(node, haveRepresentationError ? null : bytesBuilder);
 
            bytesBuilder.Free();
            return result;
        }
 
        private static bool IsFloatingPointExpressionOfUnknownPrecision(BoundExpression rewrittenNode)
        {
            if (rewrittenNode == null)
            {
                return false;
            }
 
            if (rewrittenNode.ConstantValueOpt != null)
            {
                return false;
            }
 
            Debug.Assert(rewrittenNode.Type is { });
            var type = rewrittenNode.Type;
            if (type.SpecialType != SpecialType.System_Double && type.SpecialType != SpecialType.System_Single)
            {
                return false;
            }
 
            switch (rewrittenNode.Kind)
            {
                // ECMA-335   I.12.1.3 Handling of floating-point data types.
                //    ... the value might be retained in the internal representation
                //   for future use, if it is reloaded from the storage location without having been modified ...
                //
                // Unfortunately, the above means that precision is not guaranteed even when loading from storage.
                //
                //case BoundKind.FieldAccess:
                //case BoundKind.ArrayAccess:
                //  return true;
 
                case BoundKind.Sequence:
                    var sequence = (BoundSequence)rewrittenNode;
                    return IsFloatingPointExpressionOfUnknownPrecision(sequence.Value);
 
                case BoundKind.Conversion:
                    // lowered conversions have definite precision unless they are implicit identity casts
                    var conversion = (BoundConversion)rewrittenNode;
                    return conversion.ConversionKind == ConversionKind.Identity && !conversion.ExplicitCastInCode;
            }
 
            // it is a float/double expression and we have no idea ...
            return true;
        }
 
        /// <summary>
        /// Helper method to generate a lowered conversion.
        /// </summary>
        private BoundExpression MakeConversionNode(
            BoundConversion? oldNodeOpt,
            SyntaxNode syntax,
            BoundExpression rewrittenOperand,
            Conversion conversion,
            bool @checked,
            bool explicitCastInCode,
            ConstantValue? constantValueOpt,
            TypeSymbol rewrittenType)
        {
            var result = MakeConversionNodeCore(oldNodeOpt, syntax, rewrittenOperand, conversion, @checked, explicitCastInCode, constantValueOpt, rewrittenType);
            Debug.Assert(result.Type is { } rt && rt.Equals(rewrittenType, TypeCompareKind.AllIgnoreOptions));
 
            // 4.1.6 C# spec: To force a value of a floating point type to the exact precision of its type, an explicit cast can be used.
            // It means that explicit casts to (double) or (float) should be preserved on the node.
            // If original conversion has become something else with unknown precision, add an explicit identity cast.
            if (!_inExpressionLambda &&
                explicitCastInCode &&
                IsFloatingPointExpressionOfUnknownPrecision(result))
            {
                result = new BoundConversion(
                    syntax,
                    result,
                    Conversion.Identity,
                    isBaseConversion: false,
                    @checked: false,
                    explicitCastInCode: true,
                    conversionGroupOpt: null,
                    constantValueOpt: null,
                    type: result.Type);
            }
 
            return result;
        }
 
        private BoundExpression MakeConversionNodeCore(
            BoundConversion? oldNodeOpt,
            SyntaxNode syntax,
            BoundExpression rewrittenOperand,
            Conversion conversion,
            bool @checked,
            bool explicitCastInCode,
            ConstantValue? constantValueOpt,
            TypeSymbol rewrittenType)
        {
            Debug.Assert(oldNodeOpt == null || oldNodeOpt.Syntax == syntax);
            Debug.Assert(rewrittenType is { });
            Debug.Assert(_factory.ModuleBuilderOpt is { });
            Debug.Assert(_diagnostics.DiagnosticBag is { });
 
            if (_inExpressionLambda && !conversion.IsUserDefined)
            {
                @checked = @checked && NeedsCheckedConversionInExpressionTree(rewrittenOperand.Type, rewrittenType, explicitCastInCode);
            }
 
            switch (conversion.Kind)
            {
                case ConversionKind.Identity:
                    Debug.Assert(rewrittenOperand.Type is { });
 
                    // 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.
 
                    // Why ignoreDynamic: false?
                    // Lowering phase treats object and dynamic as equivalent types. So we don't need to produce any conversion here,
                    // but we need to change the Type property on the resulting BoundExpression to match the rewrittenType.
                    // This is necessary so that subsequent lowering transformations see that the expression is dynamic.
 
                    if (_inExpressionLambda || !rewrittenOperand.Type.Equals(rewrittenType, TypeCompareKind.ConsiderEverything))
                    {
                        break;
                    }
 
                    if (!explicitCastInCode)
                    {
                        return rewrittenOperand;
                    }
 
                    // 4.1.6 C# spec: To force a value of a floating point type to the exact precision of its type, an explicit cast can be used.
                    // If this is not an identity conversion of a float with unknown precision, strip away the identity conversion.
                    if (!IsFloatingPointExpressionOfUnknownPrecision(rewrittenOperand))
                    {
                        return rewrittenOperand;
                    }
 
                    break;
 
                case ConversionKind.ExplicitUserDefined:
                case ConversionKind.ImplicitUserDefined:
                    return RewriteUserDefinedConversion(
                        syntax: syntax,
                        rewrittenOperand: rewrittenOperand,
                        conversion: conversion,
                        @checked: @checked,
                        rewrittenType: rewrittenType);
 
                case ConversionKind.IntPtr:
                    return RewriteIntPtrConversion(syntax, rewrittenOperand, conversion, @checked,
                        explicitCastInCode, constantValueOpt, rewrittenType);
 
                case ConversionKind.ImplicitNullable:
                case ConversionKind.ExplicitNullable:
                    return RewriteNullableConversion(
                        syntax: syntax,
                        rewrittenOperand: rewrittenOperand,
                        conversion: conversion,
                        @checked: @checked,
                        explicitCastInCode: explicitCastInCode,
                        rewrittenType: rewrittenType);
 
                case ConversionKind.Boxing:
 
                    if (!_inExpressionLambda)
                    {
                        // We can perform some optimizations if we have a nullable value type
                        // as the operand and we know its nullability:
 
                        // * (object)new int?() is the same as (object)null
                        // * (object)new int?(123) is the same as (object)123
 
                        if (NullableNeverHasValue(rewrittenOperand))
                        {
                            return new BoundDefaultExpression(syntax, rewrittenType);
                        }
 
                        BoundExpression? nullableValue = NullableAlwaysHasValue(rewrittenOperand);
                        if (nullableValue != null)
                        {
                            // Recurse, eliminating the unnecessary ctor.
                            return MakeConversionNode(oldNodeOpt, syntax, nullableValue, conversion, @checked, explicitCastInCode, constantValueOpt, rewrittenType);
                        }
                    }
                    break;
 
                case ConversionKind.NullLiteral:
                case ConversionKind.DefaultLiteral:
                    if (!_inExpressionLambda || !explicitCastInCode)
                    {
                        return new BoundDefaultExpression(syntax, rewrittenType);
                    }
 
                    break;
 
                case ConversionKind.ImplicitReference:
                case ConversionKind.ExplicitReference:
                    if (rewrittenOperand.IsDefaultValue() && (!_inExpressionLambda || !explicitCastInCode))
                    {
                        return new BoundDefaultExpression(syntax, rewrittenType);
                    }
 
                    break;
 
                case ConversionKind.ImplicitConstant:
                    // implicit constant conversions under nullable conversions like "byte? x = 1;
                    // are not folded since a constant cannot be nullable.
                    // As a result these conversions can reach here.
                    // Consider them same as unchecked explicit numeric conversions
                    conversion = Conversion.ExplicitNumeric;
                    @checked = false;
                    goto case ConversionKind.ImplicitNumeric;
 
                case ConversionKind.ImplicitNumeric:
                case ConversionKind.ExplicitNumeric:
                    Debug.Assert(rewrittenOperand.Type is { });
                    if (rewrittenOperand.IsDefaultValue() && (!_inExpressionLambda || !explicitCastInCode))
                    {
                        return new BoundDefaultExpression(syntax, rewrittenType);
                    }
 
                    if (rewrittenType.SpecialType == SpecialType.System_Decimal || rewrittenOperand.Type.SpecialType == SpecialType.System_Decimal)
                    {
                        return RewriteDecimalConversion(syntax, rewrittenOperand, rewrittenOperand.Type, rewrittenType, @checked, conversion.Kind.IsImplicitConversion(), constantValueOpt);
                    }
                    break;
 
                case ConversionKind.ImplicitTupleLiteral:
                case ConversionKind.ExplicitTupleLiteral:
                    {
                        Debug.Assert(rewrittenOperand.Type is { });
                        // we keep tuple literal conversions in the tree for the purpose of semantic model (for example when they are casts in the source)
                        // for the purpose of lowering/codegeneration they are identity conversions.
                        Debug.Assert(rewrittenOperand.Type.Equals(rewrittenType, TypeCompareKind.IgnoreDynamicAndTupleNames | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes));
                        return rewrittenOperand;
                    }
 
                case ConversionKind.ImplicitThrow:
                    {
                        // the operand must be a bound throw expression
                        var operand = (BoundThrowExpression)rewrittenOperand;
                        return _factory.ThrowExpression(operand.Expression, rewrittenType);
                    }
 
                case ConversionKind.ImplicitEnumeration:
                    // A conversion from constant zero to nullable is actually classified as an
                    // implicit enumeration conversion, not an implicit nullable conversion.
                    // Lower it to (E?)(E)0.
                    if (rewrittenType.IsNullableType())
                    {
                        var operand = MakeConversionNode(
                            oldNodeOpt,
                            syntax,
                            rewrittenOperand,
                            conversion,
                            @checked,
                            explicitCastInCode,
                            constantValueOpt,
                            rewrittenType.GetNullableUnderlyingType());
 
                        var outerConversion = Conversion.ImplicitNullableWithIdentityUnderlying;
                        outerConversion.MarkUnderlyingConversionsChecked();
                        return MakeConversionNode(
                            oldNodeOpt,
                            syntax,
                            operand,
                            outerConversion,
                            @checked,
                            explicitCastInCode,
                            constantValueOpt,
                            rewrittenType);
                    }
 
                    goto case ConversionKind.ExplicitEnumeration;
 
                case ConversionKind.ExplicitEnumeration:
                    Debug.Assert(rewrittenOperand.Type is { });
                    if (!rewrittenType.IsNullableType() &&
                        rewrittenOperand.IsDefaultValue() &&
                        (!_inExpressionLambda || !explicitCastInCode))
                    {
                        return new BoundDefaultExpression(syntax, rewrittenType);
                    }
 
                    if (rewrittenType.SpecialType == SpecialType.System_Decimal)
                    {
                        Debug.Assert(rewrittenOperand.Type.IsEnumType());
                        var underlyingTypeFrom = rewrittenOperand.Type.GetEnumUnderlyingType()!;
                        rewrittenOperand = MakeConversionNode(rewrittenOperand, underlyingTypeFrom, false);
                        return RewriteDecimalConversion(syntax, rewrittenOperand, underlyingTypeFrom, rewrittenType, @checked, isImplicit: false, constantValueOpt: constantValueOpt);
                    }
                    else if (rewrittenOperand.Type.SpecialType == SpecialType.System_Decimal)
                    {
                        // This is where we handle conversion from Decimal to Enum: e.g., E e = (E) d;
                        // where 'e' is of type Enum E and 'd' is of type Decimal.
                        // Conversion can be simply done by applying its underlying numeric type to RewriteDecimalConversion().
 
                        Debug.Assert(rewrittenType.IsEnumType());
                        var underlyingTypeTo = rewrittenType.GetEnumUnderlyingType()!;
                        var rewrittenNode = RewriteDecimalConversion(syntax, rewrittenOperand, rewrittenOperand.Type, underlyingTypeTo, @checked, isImplicit: false, constantValueOpt: constantValueOpt);
 
                        // However, the type of the rewritten node becomes underlying numeric type, not Enum type,
                        // which violates the overall constraint saying the type cannot be changed during rewriting (see LocalRewriter.cs).
 
                        // Instead of loosening this constraint, we return BoundConversion from underlying numeric type to Enum type,
                        // which will be eliminated during emitting (see EmitEnumConversion): e.g., E e = (E)(int) d;
                        return new BoundConversion(
                            syntax,
                            rewrittenNode,
                            conversion,
                            isBaseConversion: false,
                            @checked: false,
                            explicitCastInCode: explicitCastInCode,
                            conversionGroupOpt: oldNodeOpt?.ConversionGroupOpt,
                            constantValueOpt: constantValueOpt,
                            type: rewrittenType);
                    }
 
                    break;
 
                case ConversionKind.ImplicitDynamic:
                case ConversionKind.ExplicitDynamic:
                    Debug.Assert(conversion.Method is null);
                    Debug.Assert(!conversion.IsExtensionMethod);
                    Debug.Assert(constantValueOpt == null);
                    return _dynamicFactory.MakeDynamicConversion(rewrittenOperand, explicitCastInCode || conversion.Kind == ConversionKind.ExplicitDynamic, conversion.IsArrayIndex, @checked, rewrittenType).ToExpression();
 
                case ConversionKind.ImplicitTuple:
                case ConversionKind.ExplicitTuple:
                    return RewriteTupleConversion(
                        syntax: syntax,
                        rewrittenOperand: rewrittenOperand,
                        conversion: conversion,
                        @checked: @checked,
                        explicitCastInCode: explicitCastInCode,
                        rewrittenType: (NamedTypeSymbol)rewrittenType);
 
                case ConversionKind.MethodGroup when oldNodeOpt is { Type: { TypeKind: TypeKind.FunctionPointer } funcPtrType }:
                    {
                        var mg = (BoundMethodGroup)rewrittenOperand;
                        MethodSymbol? symbolOpt = oldNodeOpt.SymbolOpt;
                        Debug.Assert(symbolOpt is { });
                        return new BoundFunctionPointerLoad(oldNodeOpt.Syntax, symbolOpt,
                                                            constrainedToTypeOpt: symbolOpt.IsStatic &&
                                                                                  (symbolOpt.IsAbstract || symbolOpt.IsVirtual) ? mg.ReceiverOpt?.Type : null,
                                                            type: funcPtrType, hasErrors: false);
                    }
 
                case ConversionKind.MethodGroup:
                    {
                        // we eliminate the method group conversion entirely from the bound nodes following local lowering
                        Debug.Assert(oldNodeOpt is { });
                        var mg = (BoundMethodGroup)rewrittenOperand;
                        var method = oldNodeOpt.SymbolOpt;
                        Debug.Assert(method is { });
                        var oldSyntax = _factory.Syntax;
                        _factory.Syntax = (mg.ReceiverOpt ?? mg).Syntax;
                        var receiver = (!method.RequiresInstanceReceiver && !oldNodeOpt.IsExtensionMethod && !method.IsAbstract && !method.IsVirtual) ? _factory.Type(method.ContainingType) : mg.ReceiverOpt;
                        Debug.Assert(receiver is { });
                        _factory.Syntax = oldSyntax;
 
                        var boundDelegateCreation = new BoundDelegateCreationExpression(syntax, argument: receiver, methodOpt: method,
                                                                                        isExtensionMethod: oldNodeOpt.IsExtensionMethod, wasTargetTyped: false, type: rewrittenType);
 
                        EnsureParamCollectionAttributeExists(rewrittenOperand.Syntax, rewrittenType);
                        Debug.Assert(_factory.TopLevelMethod is { });
 
                        if (_factory.Compilation.LanguageVersion >= MessageID.IDS_FeatureCacheStaticMethodGroupConversion.RequiredVersion()
                            && !_inExpressionLambda // The tree structure / meaning for expression trees should remain untouched.
                            && _factory.TopLevelMethod.MethodKind != MethodKind.StaticConstructor // Avoid caching twice if people do it manually.
                            && DelegateCacheRewriter.CanRewrite(boundDelegateCreation))
                        {
                            var rewriter = _lazyDelegateCacheRewriter ??= new DelegateCacheRewriter(_factory, _topLevelMethodOrdinal);
                            return rewriter.Rewrite(boundDelegateCreation);
                        }
                        else
                        {
                            return boundDelegateCreation;
                        }
                    }
 
                case ConversionKind.InlineArray:
                    {
                        Debug.Assert(rewrittenOperand.Type is not null);
 
                        NamedTypeSymbol spanType = (NamedTypeSymbol)rewrittenType;
                        MethodSymbol createSpan;
 
                        if (spanType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions))
                        {
                            createSpan = _factory.ModuleBuilderOpt.EnsureInlineArrayAsReadOnlySpanExists(syntax, spanType.OriginalDefinition, _factory.SpecialType(SpecialType.System_Int32), _diagnostics.DiagnosticBag);
                        }
                        else
                        {
                            Debug.Assert(spanType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_Span_T), TypeCompareKind.AllIgnoreOptions));
                            createSpan = _factory.ModuleBuilderOpt.EnsureInlineArrayAsSpanExists(syntax, spanType.OriginalDefinition, _factory.SpecialType(SpecialType.System_Int32), _diagnostics.DiagnosticBag);
                        }
 
                        createSpan = createSpan.Construct(rewrittenOperand.Type, spanType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Single().Type);
                        _ = rewrittenOperand.Type.HasInlineArrayAttribute(out int length);
 
                        return _factory.Call(null, createSpan, rewrittenOperand, _factory.Literal(length), useStrictArgumentRefKinds: true);
                    }
 
                default:
                    break;
            }
 
            return oldNodeOpt != null ?
                oldNodeOpt.Update(
                    rewrittenOperand,
                    conversion,
                    isBaseConversion: oldNodeOpt.IsBaseConversion,
                    @checked: @checked,
                    explicitCastInCode: explicitCastInCode,
                    conversionGroupOpt: oldNodeOpt.ConversionGroupOpt,
                    constantValueOpt: constantValueOpt,
                    type: rewrittenType) :
                new BoundConversion(
                    syntax,
                    rewrittenOperand,
                    conversion,
                    isBaseConversion: false,
                    @checked: @checked,
                    explicitCastInCode: explicitCastInCode,
                    conversionGroupOpt: null, // BoundConversion.ConversionGroup is not used in lowered tree
                    constantValueOpt: constantValueOpt,
                    type: rewrittenType);
        }
 
        private void EnsureParamCollectionAttributeExists(SyntaxNode node, TypeSymbol delegateType)
        {
            Debug.Assert(_factory.ModuleBuilderOpt is { });
 
            if (delegateType.IsAnonymousType && delegateType.ContainingModule == _compilation.SourceModule &&
                delegateType.DelegateInvokeMethod() is MethodSymbol delegateInvoke &&
                delegateInvoke.Parameters.Any(static (p) => p.IsParamsCollection))
            {
                _factory.ModuleBuilderOpt.EnsureParamCollectionAttributeExists(_diagnostics, node.Location);
            }
        }
 
        // Determine if the conversion can actually overflow at runtime.  If not, no need to generate a checked instruction.
        private static bool NeedsCheckedConversionInExpressionTree(TypeSymbol? source, TypeSymbol target, bool explicitCastInCode)
        {
            Debug.Assert((object)target != null);
 
            if (source is null)
            {
                return false;
            }
 
            SpecialType GetUnderlyingSpecialType(TypeSymbol type) =>
                type.StrippedType().EnumUnderlyingTypeOrSelf().SpecialType;
 
            bool IsInRange(SpecialType type, SpecialType low, SpecialType high) =>
                low <= type && type <= high;
 
            SpecialType sourceST = GetUnderlyingSpecialType(source);
            SpecialType targetST = GetUnderlyingSpecialType(target);
 
            // integral to double or float is never checked, but float/double to integral
            // may be checked.
            return (explicitCastInCode || sourceST != targetST) &&
                IsInRange(sourceST, SpecialType.System_Char, SpecialType.System_Double) &&
                IsInRange(targetST, SpecialType.System_Char, SpecialType.System_UInt64);
        }
 
        /// <summary>
        /// Helper method to generate a lowered conversion from the given <paramref name="rewrittenOperand"/> to the given <paramref name="rewrittenType"/>.
        /// </summary>
        /// <remarks>
        /// If we're converting a default parameter value to the parameter type, then the conversion can actually fail
        /// (e.g. if the default value was specified by an attribute and was, therefore, not checked by the compiler).
        /// Set acceptFailingConversion if you want to see default(rewrittenType) in such cases.
        /// The error will be suppressed only for conversions from <see cref="decimal"/> or <see cref="DateTime"/>.
        /// </remarks>
        private BoundExpression MakeConversionNode(BoundExpression rewrittenOperand, TypeSymbol rewrittenType, bool @checked, bool acceptFailingConversion = false, bool markAsChecked = false)
        {
            Conversion conversion = MakeConversion(rewrittenOperand, rewrittenType, @checked: @checked, _compilation, _diagnostics, acceptFailingConversion);
            if (!conversion.IsValid)
            {
                return _factory.NullOrDefault(rewrittenType);
            }
 
#if DEBUG
            if (markAsChecked)
            {
                conversion.MarkUnderlyingConversionsCheckedRecursive();
            }
#endif
 
            return MakeConversionNode(rewrittenOperand.Syntax, rewrittenOperand, conversion, rewrittenType, @checked);
        }
 
        private static Conversion MakeConversion(
            BoundExpression rewrittenOperand,
            TypeSymbol rewrittenType,
            bool @checked,
            CSharpCompilation compilation,
            BindingDiagnosticBag diagnostics,
            bool acceptFailingConversion)
        {
            Debug.Assert(rewrittenOperand.Type is { });
            var useSiteInfo = new CompoundUseSiteInfo<AssemblySymbol>(diagnostics, compilation.Assembly);
            Conversion conversion = compilation.Conversions.ClassifyConversionFromType(rewrittenOperand.Type, rewrittenType, isChecked: @checked, ref useSiteInfo);
            diagnostics.Add(rewrittenOperand.Syntax, useSiteInfo);
 
            if (!conversion.IsValid)
            {
                if (!acceptFailingConversion ||
                     rewrittenOperand.Type.SpecialType != SpecialType.System_Decimal &&
                     rewrittenOperand.Type.SpecialType != SpecialType.System_DateTime)
                {
                    // error CS0029: Cannot implicitly convert type '{0}' to '{1}'
                    diagnostics.Add(
                        ErrorCode.ERR_NoImplicitConv,
                        rewrittenOperand.Syntax.Location,
                        rewrittenOperand.Type,
                        rewrittenType);
                }
            }
 
            return conversion;
        }
 
        private BoundExpression MakeConversionNode(
            SyntaxNode syntax,
            BoundExpression rewrittenOperand,
            Conversion conversion,
            TypeSymbol rewrittenType,
            bool @checked,
            bool explicitCastInCode = false,
            ConstantValue? constantValueOpt = null)
        {
            Debug.Assert(conversion.IsValid);
 
            // Typically by the time we get here, a user-defined conversion has been realized as a sequence of
            // conversions that convert to the exact parameter type of the user-defined conversion,
            // and then from the exact return type of the user-defined conversion to the desired type.
            // However, it is possible that we have cached a conversion (for example, to be used
            // in an increment or decrement operator) and are only just realizing it now.
            //
            // Due to an oddity in the way we create a non-lifted user-defined conversion from A to D?
            // (required backwards compatibility with the native compiler) we can end up in a situation
            // where we have:
            //
            // a standard conversion from A to B?
            // then a standard conversion from B? to B
            // then a user-defined  conversion from B to C
            // then a standard conversion from C to C?
            // then a standard conversion from C? to D?
            //
            // In that scenario, the "from type" of the conversion will be B? and the "from conversion" will be
            // from A to B?. Similarly the "to type" of the conversion will be C? and the "to conversion"
            // of the conversion will be from C? to D?. We still need to induce the conversions from B? to B
            // and from C to C?.
 
            if (conversion.Kind.IsUserDefinedConversion())
            {
                Debug.Assert(conversion.Method is { });
                Debug.Assert(conversion.BestUserDefinedConversionAnalysis is { });
                conversion.AssertUnderlyingConversionsCheckedRecursive();
 
                if (!TypeSymbol.Equals(rewrittenOperand.Type, conversion.BestUserDefinedConversionAnalysis.FromType, TypeCompareKind.ConsiderEverything2))
                {
                    rewrittenOperand = MakeConversionNode(
                        syntax,
                        rewrittenOperand,
                        conversion.UserDefinedFromConversion,
                        conversion.BestUserDefinedConversionAnalysis.FromType,
                        @checked);
                }
 
                if (!TypeSymbol.Equals(rewrittenOperand.Type, conversion.Method.GetParameterType(0), TypeCompareKind.ConsiderEverything2))
                {
                    rewrittenOperand = MakeConversionNode(
                        rewrittenOperand,
                        conversion.BestUserDefinedConversionAnalysis.FromType,
                        @checked,
                        markAsChecked: true);
                }
 
                TypeSymbol userDefinedConversionRewrittenType = conversion.Method.ReturnType;
 
                // Lifted conversion, wrap return type in Nullable
                // The conversion only needs to happen for non-nullable valuetypes
                Debug.Assert(rewrittenOperand.Type is { });
                if (rewrittenOperand.Type.IsNullableType() &&
                        conversion.Method.GetParameterType(0).Equals(rewrittenOperand.Type.GetNullableUnderlyingType(), TypeCompareKind.AllIgnoreOptions) &&
                        !userDefinedConversionRewrittenType.IsNullableType() &&
                        userDefinedConversionRewrittenType.IsValueType)
                {
                    userDefinedConversionRewrittenType = ((NamedTypeSymbol)rewrittenOperand.Type.OriginalDefinition).Construct(userDefinedConversionRewrittenType);
                }
 
                BoundExpression userDefined = RewriteUserDefinedConversion(
                    syntax,
                    rewrittenOperand,
                    conversion,
                    @checked: @checked,
                    userDefinedConversionRewrittenType);
 
                if (!TypeSymbol.Equals(userDefined.Type, conversion.BestUserDefinedConversionAnalysis.ToType, TypeCompareKind.ConsiderEverything2))
                {
                    userDefined = MakeConversionNode(
                        userDefined,
                        conversion.BestUserDefinedConversionAnalysis.ToType,
                        @checked,
                        markAsChecked: true);
                }
 
                if (!TypeSymbol.Equals(userDefined.Type, rewrittenType, TypeCompareKind.ConsiderEverything2))
                {
                    userDefined = MakeConversionNode(
                        syntax,
                        userDefined,
                        conversion.UserDefinedToConversion,
                        rewrittenType,
                        @checked);
                }
 
                return userDefined;
            }
 
            return MakeConversionNode(
                oldNodeOpt: null,
                syntax: syntax,
                rewrittenOperand: rewrittenOperand,
                conversion: conversion,
                @checked: @checked,
                explicitCastInCode: explicitCastInCode,
                constantValueOpt: constantValueOpt,
                rewrittenType: rewrittenType);
        }
 
        private BoundExpression RewriteTupleConversion(
            SyntaxNode syntax,
            BoundExpression rewrittenOperand,
            Conversion conversion,
            bool @checked,
            bool explicitCastInCode,
            NamedTypeSymbol rewrittenType)
        {
            Debug.Assert(rewrittenOperand.Type is { });
            var destElementTypes = rewrittenType.TupleElementTypesWithAnnotations;
            var numElements = destElementTypes.Length;
 
            var tupleTypeSymbol = (NamedTypeSymbol)rewrittenOperand.Type;
            var srcElementFields = tupleTypeSymbol.TupleElements;
            var fieldAccessorsBuilder = ArrayBuilder<BoundExpression>.GetInstance(numElements);
 
            BoundAssignmentOperator assignmentToTemp;
            var savedTuple = _factory.StoreToTemp(rewrittenOperand, out assignmentToTemp);
            var elementConversions = conversion.UnderlyingConversions;
            conversion.AssertUnderlyingConversionsChecked();
 
            for (int i = 0; i < numElements; i++)
            {
                var fieldAccess = MakeTupleFieldAccessAndReportUseSiteDiagnostics(savedTuple, syntax, srcElementFields[i]);
                var convertedFieldAccess = MakeConversionNode(syntax, fieldAccess, elementConversions[i], destElementTypes[i].Type, @checked, explicitCastInCode);
                fieldAccessorsBuilder.Add(convertedFieldAccess);
            }
 
            var result = MakeTupleCreationExpression(syntax, rewrittenType, fieldAccessorsBuilder.ToImmutableAndFree());
            return _factory.MakeSequence(savedTuple.LocalSymbol, assignmentToTemp, result);
        }
 
        internal static bool NullableNeverHasValue(BoundExpression expression)
        {
            // CONSIDER: A sequence of side effects with an always-null expression as its value
            // CONSIDER: can be optimized also. Should we?
 
            return expression.NullableNeverHasValue();
        }
 
        /// <summary>
        /// If the nullable expression always has a value, returns the value, otherwise null.
        /// This is normally performed on a lowered expression, however for the purpose of
        /// tuples and tuple equality operators, we do this on a partially lowered expression in
        /// which conversions appearing at the top of the expression have not been lowered.
        /// If this method is updated to recognize more complex patterns, callers should be reviewed.
        /// </summary>
        internal static BoundExpression? NullableAlwaysHasValue(BoundExpression expression)
        {
            Debug.Assert(expression.Type is { });
            if (!expression.Type.IsNullableType())
                return null;
 
            switch (expression)
            {
                // Detect the lowered nullable conversion from value type K to type Nullable<K>
                case BoundObjectCreationExpression { Arguments: { Length: 1 } args }:
                    return args[0];
 
                // Detect the unlowered nullable conversion from value type K to type Nullable<K>
                // This arises in lowering tuple equality operators
                case BoundConversion { Conversion: { Kind: ConversionKind.ImplicitNullable }, Operand: var convertedArgument }
                        when convertedArgument.Type!.Equals(expression.Type.StrippedType(), TypeCompareKind.AllIgnoreOptions):
                    return convertedArgument;
 
                // Detect the unlowered nullable conversion from a tuple type T1 to Nullable<T2> for a tuple type T2.
                case BoundConversion { Conversion: { Kind: ConversionKind.ImplicitNullable, UnderlyingConversions: var underlying }, Operand: var convertedArgument } conversion
                        when underlying.Length == 1 && underlying[0].Kind == ConversionKind.ImplicitTuple && !convertedArgument.Type!.IsNullableType():
 
                    conversion.Conversion.AssertUnderlyingConversionsChecked();
                    return new BoundConversion(
                        syntax: expression.Syntax,
                        operand: convertedArgument,
                        conversion: underlying[0],
                        @checked: conversion.Checked,
                        explicitCastInCode: conversion.ExplicitCastInCode,
                        conversionGroupOpt: null,
                        constantValueOpt: null,
                        type: conversion.Type.StrippedType(),
                        hasErrors: conversion.HasErrors);
 
                // No other cases are recognized
                default:
                    return null;
            }
        }
 
        private BoundExpression RewriteNullableConversion(
            SyntaxNode syntax,
            BoundExpression rewrittenOperand,
            Conversion conversion,
            bool @checked,
            bool explicitCastInCode,
            TypeSymbol rewrittenType)
        {
            Debug.Assert((object)rewrittenType != null);
 
            if (_inExpressionLambda)
            {
                return RewriteLiftedConversionInExpressionTree(syntax, rewrittenOperand, conversion, @checked, explicitCastInCode, rewrittenType);
            }
 
            TypeSymbol? rewrittenOperandType = rewrittenOperand.Type;
            Debug.Assert(rewrittenOperandType is { });
            Debug.Assert(rewrittenType.IsNullableType() || rewrittenOperandType.IsNullableType());
 
            if (rewrittenOperandType.IsNullableType() && rewrittenType.IsNullableType())
            {
                return RewriteFullyLiftedBuiltInConversion(syntax, rewrittenOperand, conversion, @checked, rewrittenType);
            }
            else if (rewrittenType.IsNullableType())
            {
                // SPEC: If the nullable conversion is from S to T?, the conversion is
                // SPEC: evaluated as the underlying conversion from S to T followed
                // SPEC: by a wrapping from T to T?.
 
                conversion.AssertUnderlyingConversionsChecked();
                BoundExpression rewrittenConversion = MakeConversionNode(syntax, rewrittenOperand, conversion.UnderlyingConversions[0], rewrittenType.GetNullableUnderlyingType(), @checked);
                MethodSymbol ctor = UnsafeGetNullableMethod(syntax, rewrittenType, SpecialMember.System_Nullable_T__ctor);
                return new BoundObjectCreationExpression(syntax, ctor, rewrittenConversion);
            }
            else
            {
                // SPEC: if the nullable conversion is from S? to T, the conversion is
                // SPEC: evaluated as an unwrapping from S? to S followed by the underlying
                // SPEC: conversion from S to T.
 
                // We can do a simple optimization here if we know that the source is never null:
 
                BoundExpression? value = NullableAlwaysHasValue(rewrittenOperand);
                if (value == null)
                {
                    // (If the source is known to be possibly null then we need to keep the call to get Value
                    // in place so that it throws at runtime.)
                    MethodSymbol get_Value = UnsafeGetNullableMethod(syntax, rewrittenOperandType, SpecialMember.System_Nullable_T_get_Value);
                    value = BoundCall.Synthesized(syntax, rewrittenOperand, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, get_Value);
                }
 
                conversion.AssertUnderlyingConversionsChecked();
                return MakeConversionNode(syntax, value, conversion.UnderlyingConversions[0], rewrittenType, @checked);
            }
        }
 
        private BoundExpression RewriteLiftedConversionInExpressionTree(
            SyntaxNode syntax,
            BoundExpression rewrittenOperand,
            Conversion conversion,
            bool @checked,
            bool explicitCastInCode,
            TypeSymbol rewrittenType)
        {
            Debug.Assert((object)rewrittenType != null);
            Debug.Assert(rewrittenOperand.Type is { });
            TypeSymbol rewrittenOperandType = rewrittenOperand.Type;
            Debug.Assert(rewrittenType.IsNullableType() || rewrittenOperandType.IsNullableType());
 
            ConversionGroup? conversionGroup = null; // BoundConversion.ConversionGroup is not used in lowered tree
 
            TypeSymbol typeFrom = rewrittenOperandType.StrippedType();
            TypeSymbol typeTo = rewrittenType.StrippedType();
            if (!TypeSymbol.Equals(typeFrom, typeTo, TypeCompareKind.ConsiderEverything2) && (typeFrom.SpecialType == SpecialType.System_Decimal || typeTo.SpecialType == SpecialType.System_Decimal))
            {
                // take special care if the underlying conversion is a decimal conversion
                TypeSymbol typeFromUnderlying = typeFrom;
                TypeSymbol typeToUnderlying = typeTo;
 
                // They can't both be enums, since one of them is decimal.
                if (typeFrom.IsEnumType())
                {
                    typeFromUnderlying = typeFrom.GetEnumUnderlyingType()!;
 
                    // NOTE: Dev10 converts enum? to underlying?, rather than directly to underlying.
                    rewrittenOperandType = rewrittenOperandType.IsNullableType() ? ((NamedTypeSymbol)rewrittenOperandType.OriginalDefinition).Construct(typeFromUnderlying) : typeFromUnderlying;
                    rewrittenOperand = BoundConversion.SynthesizedNonUserDefined(syntax, rewrittenOperand, Conversion.ImplicitEnumeration, rewrittenOperandType);
                }
                else if (typeTo.IsEnumType())
                {
                    typeToUnderlying = typeTo.GetEnumUnderlyingType()!;
                }
 
                if (!TryGetSpecialTypeMethod(syntax, DecimalConversionMethod(typeFromUnderlying, typeToUnderlying), out MethodSymbol method))
                {
                    return BadExpression(syntax, rewrittenType, rewrittenOperand);
                }
 
                var conversionKind = conversion.Kind.IsImplicitConversion() ? ConversionKind.ImplicitUserDefined : ConversionKind.ExplicitUserDefined;
                var result = new BoundConversion(syntax, rewrittenOperand, new Conversion(conversionKind, method, false), @checked, explicitCastInCode: explicitCastInCode, conversionGroup, constantValueOpt: null, rewrittenType);
                return result;
            }
            else
            {
                return new BoundConversion(syntax, rewrittenOperand, conversion, @checked, explicitCastInCode: explicitCastInCode, conversionGroup, constantValueOpt: null, rewrittenType);
            }
        }
 
        private BoundExpression RewriteFullyLiftedBuiltInConversion(
            SyntaxNode syntax,
            BoundExpression operand,
            Conversion conversion,
            bool @checked,
            TypeSymbol type)
        {
            // SPEC: If the nullable conversion is from S? to T?:
            // SPEC: * If the source HasValue property is false the result
            // SPEC:   is a null value of type T?.
            // SPEC: * Otherwise the conversion is evaluated as an unwrapping
            // SPEC:   from S? to S, followed by the underlying conversion from
            // SPEC:   S to T, followed by a wrapping from T to T?
 
            BoundExpression? optimized = OptimizeLiftedBuiltInConversion(syntax, operand, conversion, @checked, type);
            if (optimized != null)
            {
                return optimized;
            }
 
            // We are unable to optimize the conversion. "(T?)s" is generated as:
            // S? temp = s;
            // temp.HasValue ? new T?((T)temp.GetValueOrDefault()) : default(T?)
 
            BoundAssignmentOperator tempAssignment;
            var boundTemp = _factory.StoreToTemp(operand, out tempAssignment);
            MethodSymbol getValueOrDefault;
 
            if (!TryGetNullableMethod(syntax, boundTemp.Type, SpecialMember.System_Nullable_T_GetValueOrDefault, out getValueOrDefault))
            {
                return BadExpression(syntax, type, operand);
            }
 
            BoundExpression condition = MakeNullableHasValue(syntax, boundTemp);
            conversion.AssertUnderlyingConversionsChecked();
            BoundExpression consequence = new BoundObjectCreationExpression(
                syntax,
                UnsafeGetNullableMethod(syntax, type, SpecialMember.System_Nullable_T__ctor),
                MakeConversionNode(
                    syntax,
                    BoundCall.Synthesized(syntax, boundTemp, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, getValueOrDefault),
                    conversion.UnderlyingConversions[0],
                    type.GetNullableUnderlyingType(),
                    @checked));
            BoundExpression alternative = new BoundDefaultExpression(syntax, type);
            BoundExpression conditionalExpression = RewriteConditionalOperator(
                syntax: syntax,
                rewrittenCondition: condition,
                rewrittenConsequence: consequence,
                rewrittenAlternative: alternative,
                constantValueOpt: null,
                rewrittenType: type,
                isRef: false);
 
            return new BoundSequence(
                syntax: syntax,
                locals: ImmutableArray.Create(boundTemp.LocalSymbol),
                sideEffects: ImmutableArray.Create<BoundExpression>(tempAssignment),
                value: conditionalExpression,
                type: type);
        }
 
        private BoundExpression? OptimizeLiftedUserDefinedConversion(
            SyntaxNode syntax,
            BoundExpression operand,
            Conversion conversion,
            TypeSymbol type)
        {
            // We begin with some optimizations: if the converted expression is known to always be null
            // then we can skip the whole thing and simply return the alternative:
 
            if (NullableNeverHasValue(operand))
            {
                return new BoundDefaultExpression(syntax, type);
            }
 
            // If the converted expression is known to never be null then we can return
            // new R?(op_Whatever(nonNullableValue))
            BoundExpression? nonNullValue = NullableAlwaysHasValue(operand);
            if (nonNullValue != null)
            {
                Debug.Assert(conversion.Method is { });
                var constrainedToTypeOpt = conversion.ConstrainedToTypeOpt;
                return MakeLiftedUserDefinedConversionConsequence(BoundCall.Synthesized(
                    syntax,
                    receiverOpt: constrainedToTypeOpt is null ? null : new BoundTypeExpression(syntax, aliasOpt: null, constrainedToTypeOpt),
                    initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown,
                    conversion.Method,
                    nonNullValue), type);
            }
 
            return DistributeLiftedConversionIntoLiftedOperand(syntax, operand, conversion, false, type);
        }
 
        private BoundExpression? OptimizeLiftedBuiltInConversion(
            SyntaxNode syntax,
            BoundExpression operand,
            Conversion conversion,
            bool @checked,
            TypeSymbol type)
        {
            Debug.Assert(operand != null);
            Debug.Assert((object)type != null);
 
            // First, an optimization. If the source is known to always be null then
            // we can simply return the alternative.
 
            if (NullableNeverHasValue(operand))
            {
                return new BoundDefaultExpression(syntax, type);
            }
 
            // Second, a trickier optimization. If the conversion is "(T?)(new S?(x))" then
            // we generate "new T?((T)x)"
 
            BoundExpression? nonNullValue = NullableAlwaysHasValue(operand);
            if (nonNullValue != null)
            {
                conversion.AssertUnderlyingConversionsChecked();
                return new BoundObjectCreationExpression(
                    syntax,
                    UnsafeGetNullableMethod(syntax, type, SpecialMember.System_Nullable_T__ctor),
                    MakeConversionNode(
                        syntax,
                        nonNullValue,
                        conversion.UnderlyingConversions[0],
                        type.GetNullableUnderlyingType(),
                        @checked));
            }
 
            // Third, a very tricky optimization.
            return DistributeLiftedConversionIntoLiftedOperand(syntax, operand, conversion, @checked, type);
        }
 
        private BoundExpression? DistributeLiftedConversionIntoLiftedOperand(
            SyntaxNode syntax,
            BoundExpression operand,
            Conversion conversion,
            bool @checked,
            TypeSymbol type)
        {
            // Third, an even trickier optimization. Suppose we have a lifted conversion on top of
            // a lifted operation. Say, "decimal? d = M() + N()" where M() and N() return nullable ints.
            // We can codegen this naively as:
            //
            // int? m = M();
            // int? n = N();
            // int? r = m.HasValue && n.HasValue ? new int?(m.Value + n.Value) : new int?();
            // decimal? d = r.HasValue ? new decimal?((decimal)r.Value) : new decimal?();
            //
            // However, we also observe that we could do the conversion on both branches of the conditional:
            //
            // int? m = M();
            // int? n = N();
            // decimal? d = m.HasValue && n.HasValue ? (decimal?)(new int?(m.Value + n.Value)) : (decimal?)(new int?());
            //
            // And we already optimize those, above! So we could reduce this to:
            //
            // int? m = M();
            // int? n = N();
            // decimal? d = m.HasValue && n.HasValue ? new decimal?((decimal)(m.Value + n.Value)) : new decimal?());
            //
            // which avoids entirely the creation of the unnecessary nullable int!
 
            if (operand.Kind == BoundKind.Sequence)
            {
                BoundSequence seq = (BoundSequence)operand;
                if (seq.Value.Kind == BoundKind.ConditionalOperator)
                {
                    BoundConditionalOperator conditional = (BoundConditionalOperator)seq.Value;
                    Debug.Assert(TypeSymbol.Equals(seq.Type, conditional.Type, TypeCompareKind.ConsiderEverything2));
                    Debug.Assert(TypeSymbol.Equals(conditional.Type, conditional.Consequence.Type, TypeCompareKind.ConsiderEverything2));
                    Debug.Assert(TypeSymbol.Equals(conditional.Type, conditional.Alternative.Type, TypeCompareKind.ConsiderEverything2));
 
                    if (NullableAlwaysHasValue(conditional.Consequence) != null && NullableNeverHasValue(conditional.Alternative))
                    {
                        return new BoundSequence(
                            seq.Syntax,
                            seq.Locals,
                            seq.SideEffects,
                            RewriteConditionalOperator(
                                conditional.Syntax,
                                conditional.Condition,
                                MakeConversionNode(null, syntax, conditional.Consequence, conversion, @checked, explicitCastInCode: false, constantValueOpt: ConstantValue.NotAvailable, rewrittenType: type),
                                MakeConversionNode(null, syntax, conditional.Alternative, conversion, @checked, explicitCastInCode: false, constantValueOpt: ConstantValue.NotAvailable, rewrittenType: type),
                                ConstantValue.NotAvailable,
                                type,
                                isRef: false),
                            type);
                    }
                }
            }
 
            return null;
        }
 
        private BoundExpression RewriteUserDefinedConversion(
            SyntaxNode syntax,
            BoundExpression rewrittenOperand,
            Conversion conversion,
            bool @checked,
            TypeSymbol rewrittenType)
        {
            Debug.Assert(conversion.Method is { } && !conversion.Method.ReturnsVoid && conversion.Method.ParameterCount == 1);
            Debug.Assert(rewrittenOperand.Type is { });
            if (rewrittenOperand.Type.IsNullableType())
            {
                var parameterType = conversion.Method.GetParameterType(0);
                if (parameterType.Equals(rewrittenOperand.Type.GetNullableUnderlyingType(), TypeCompareKind.AllIgnoreOptions) &&
                    !parameterType.IsNullableType() &&
                    parameterType.IsValueType)
                {
                    return RewriteLiftedUserDefinedConversion(syntax, rewrittenOperand, conversion, @checked: @checked, rewrittenType);
                }
            }
 
            // do not rewrite user defined conversion in expression trees
            if (_inExpressionLambda)
            {
                return BoundConversion.Synthesized(syntax, rewrittenOperand, conversion, @checked: @checked, explicitCastInCode: true, conversionGroupOpt: null, constantValueOpt: null, rewrittenType);
            }
 
            if ((rewrittenOperand.Type.IsArray()) && _compilation.IsReadOnlySpanType(rewrittenType))
            {
                return new BoundReadOnlySpanFromArray(syntax, rewrittenOperand, conversion.Method, rewrittenType) { WasCompilerGenerated = true };
            }
 
            var constrainedToTypeOpt = conversion.ConstrainedToTypeOpt;
            BoundExpression result = BoundCall.Synthesized(
                syntax,
                receiverOpt: constrainedToTypeOpt is null ? null : new BoundTypeExpression(syntax, aliasOpt: null, constrainedToTypeOpt),
                initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown,
                conversion.Method,
                rewrittenOperand);
            Debug.Assert(TypeSymbol.Equals(result.Type, rewrittenType, TypeCompareKind.ConsiderEverything2));
            return result;
        }
 
        private BoundExpression MakeLiftedUserDefinedConversionConsequence(BoundCall call, TypeSymbol resultType)
        {
            if (call.Method.ReturnType.IsValidNullableTypeArgument())
            {
                Debug.Assert(resultType.IsNullableType() && TypeSymbol.Equals(resultType.GetNullableUnderlyingType(), call.Method.ReturnType, TypeCompareKind.ConsiderEverything2));
                MethodSymbol ctor = UnsafeGetNullableMethod(call.Syntax, resultType, SpecialMember.System_Nullable_T__ctor);
                return new BoundObjectCreationExpression(call.Syntax, ctor, call);
            }
 
            return call;
        }
 
        private BoundExpression RewriteLiftedUserDefinedConversion(
            SyntaxNode syntax,
            BoundExpression rewrittenOperand,
            Conversion conversion,
            bool @checked,
            TypeSymbol rewrittenType)
        {
            Debug.Assert(rewrittenOperand.Type is { });
            if (_inExpressionLambda)
            {
                Conversion conv = TryMakeConversion(syntax, conversion, rewrittenOperand.Type, rewrittenType, @checked: @checked);
                return BoundConversion.Synthesized(syntax, rewrittenOperand, conv, @checked: @checked, explicitCastInCode: true, conversionGroupOpt: null, constantValueOpt: null, rewrittenType);
            }
 
            // DELIBERATE SPEC VIOLATION:
            // The native compiler allows for a "lifted" conversion even when the return type of the conversion
            // not a non-nullable value type. For example, if we have a conversion from struct S to string,
            // then a "lifted" conversion from S? to string is considered by the native compiler to exist,
            // with the semantics of "s.HasValue ? (string)s.Value : (string)null".  The Roslyn compiler
            // perpetuates this error for the sake of backwards compatibility.
 
            Debug.Assert((object)rewrittenType != null);
            Debug.Assert(rewrittenOperand.Type.IsNullableType());
 
            BoundExpression? optimized = OptimizeLiftedUserDefinedConversion(syntax, rewrittenOperand, conversion, rewrittenType);
            if (optimized != null)
            {
                return optimized;
            }
 
            // We have no optimizations we can perform. If the return type of the
            // conversion method is a non-nullable value type R then we lower this as:
            //
            // temp = operand
            // temp.HasValue ? new R?(op_Whatever(temp.GetValueOrDefault())) : default(R?)
            //
            // Otherwise, if the return type of the conversion is a nullable value type, reference type
            // or pointer type P, then we lower this as:
            //
            // temp = operand
            // temp.HasValue ? op_Whatever(temp.GetValueOrDefault()) : default(P)
 
            BoundAssignmentOperator tempAssignment;
            BoundLocal boundTemp = _factory.StoreToTemp(rewrittenOperand, out tempAssignment);
            MethodSymbol getValueOrDefault = UnsafeGetNullableMethod(syntax, boundTemp.Type, SpecialMember.System_Nullable_T_GetValueOrDefault);
 
            // temp.HasValue
            BoundExpression condition = _factory.MakeNullableHasValue(syntax, boundTemp);
 
            // temp.GetValueOrDefault()
            BoundCall callGetValueOrDefault = BoundCall.Synthesized(syntax, boundTemp, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, getValueOrDefault);
 
            // op_Whatever(temp.GetValueOrDefault())
            Debug.Assert(conversion.Method is { });
            var constrainedToTypeOpt = conversion.ConstrainedToTypeOpt;
            BoundCall userDefinedCall = BoundCall.Synthesized(
                syntax,
                receiverOpt: constrainedToTypeOpt is null ? null : new BoundTypeExpression(syntax, aliasOpt: null, constrainedToTypeOpt),
                initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown,
                conversion.Method,
                callGetValueOrDefault);
 
            // new R?(op_Whatever(temp.GetValueOrDefault())
            BoundExpression consequence = MakeLiftedUserDefinedConversionConsequence(userDefinedCall, rewrittenType);
 
            // default(R?)
            BoundExpression alternative = new BoundDefaultExpression(syntax, rewrittenType);
 
            // temp.HasValue ? new R?(op_Whatever(temp.GetValueOrDefault())) : default(R?)
            BoundExpression conditionalExpression = RewriteConditionalOperator(
                syntax: syntax,
                rewrittenCondition: condition,
                rewrittenConsequence: consequence,
                rewrittenAlternative: alternative,
                constantValueOpt: null,
                rewrittenType: rewrittenType,
                isRef: false);
 
            // temp = operand
            // temp.HasValue ? new R?(op_Whatever(temp.GetValueOrDefault())) : default(R?)
            return new BoundSequence(
                syntax: syntax,
                locals: ImmutableArray.Create(boundTemp.LocalSymbol),
                sideEffects: ImmutableArray.Create<BoundExpression>(tempAssignment),
                value: conditionalExpression,
                type: rewrittenType);
        }
 
        private BoundExpression RewriteIntPtrConversion(
            SyntaxNode syntax,
            BoundExpression rewrittenOperand,
            Conversion conversion,
            bool @checked,
            bool explicitCastInCode,
            ConstantValue? constantValueOpt,
            TypeSymbol rewrittenType)
        {
            Debug.Assert(rewrittenOperand != null);
            Debug.Assert((object)rewrittenType != null);
            Debug.Assert(rewrittenOperand.Type is { });
            Debug.Assert(!_compilation.Assembly.RuntimeSupportsNumericIntPtr);
 
            TypeSymbol source = rewrittenOperand.Type;
            TypeSymbol target = rewrittenType;
 
            SpecialMember member = GetIntPtrConversionMethod(source: source, target: rewrittenType);
            MethodSymbol method;
 
            if (!TryGetSpecialTypeMethod(syntax, member, out method))
            {
                return BadExpression(syntax, rewrittenType, rewrittenOperand);
            }
 
            Debug.Assert(!method.ReturnsVoid);
            Debug.Assert(method.ParameterCount == 1);
 
            conversion = conversion.SetConversionMethod(method);
 
            if (source.IsNullableType() && target.IsNullableType())
            {
                Debug.Assert(target.IsNullableType());
                return RewriteLiftedUserDefinedConversion(syntax, rewrittenOperand, conversion, @checked: @checked, rewrittenType);
            }
            else if (source.IsNullableType())
            {
                rewrittenOperand = MakeConversionNode(rewrittenOperand, source.StrippedType(), @checked, markAsChecked: true);
            }
 
            rewrittenOperand = MakeConversionNode(rewrittenOperand, method.GetParameterType(0), @checked);
 
            if (_inExpressionLambda)
            {
                return BoundConversion.Synthesized(syntax, rewrittenOperand, conversion, @checked, explicitCastInCode: explicitCastInCode, conversionGroupOpt: null, constantValueOpt, rewrittenType);
            }
 
            var rewrittenCall = MakeCall(
                    syntax: syntax,
                    rewrittenReceiver: null,
                    method: method,
                    rewrittenArguments: ImmutableArray.Create(rewrittenOperand));
 
            return MakeConversionNode(rewrittenCall, rewrittenType, @checked, markAsChecked: true);
        }
 
        public static SpecialMember GetIntPtrConversionMethod(TypeSymbol source, TypeSymbol target)
        {
            Debug.Assert((object)source != null);
            Debug.Assert((object)target != null);
 
            TypeSymbol t0 = target.StrippedType();
            TypeSymbol s0 = source.StrippedType();
 
            SpecialType t0Type = t0.IsEnumType() ? t0.GetEnumUnderlyingType()!.SpecialType : t0.SpecialType;
            SpecialType s0Type = s0.IsEnumType() ? s0.GetEnumUnderlyingType()!.SpecialType : s0.SpecialType;
 
            if (t0Type == SpecialType.System_IntPtr)
            {
                if (source.IsPointerOrFunctionPointer())
                {
                    return SpecialMember.System_IntPtr__op_Explicit_FromPointer;
                }
 
                switch (s0Type)
                {
                    case SpecialType.System_Byte:
                    case SpecialType.System_SByte:
                    case SpecialType.System_Int16:
                    case SpecialType.System_UInt16:
                    case SpecialType.System_Char:
                    case SpecialType.System_Int32:
                        return SpecialMember.System_IntPtr__op_Explicit_FromInt32;
                    case SpecialType.System_UInt32:
                    case SpecialType.System_UInt64:
                    case SpecialType.System_Int64:
                    case SpecialType.System_Single:
                    case SpecialType.System_Double:
                    case SpecialType.System_Decimal:
                        return SpecialMember.System_IntPtr__op_Explicit_FromInt64;
                }
            }
            else if (t0Type == SpecialType.System_UIntPtr)
            {
                if (source.IsPointerOrFunctionPointer())
                {
                    return SpecialMember.System_UIntPtr__op_Explicit_FromPointer;
                }
 
                switch (s0Type)
                {
                    case SpecialType.System_Byte:
                    case SpecialType.System_UInt16:
                    case SpecialType.System_Char:
                    case SpecialType.System_UInt32:
                        return SpecialMember.System_UIntPtr__op_Explicit_FromUInt32;
                    case SpecialType.System_SByte:
                    case SpecialType.System_Int16:
                    case SpecialType.System_Int32:
                    case SpecialType.System_UInt64:
                    case SpecialType.System_Int64:
                    case SpecialType.System_Single:
                    case SpecialType.System_Double:
                    case SpecialType.System_Decimal:
                        return SpecialMember.System_UIntPtr__op_Explicit_FromUInt64;
                }
            }
            else if (s0Type == SpecialType.System_IntPtr)
            {
                if (target.IsPointerOrFunctionPointer())
                {
                    return SpecialMember.System_IntPtr__op_Explicit_ToPointer;
                }
 
                switch (t0Type)
                {
                    case SpecialType.System_Byte:
                    case SpecialType.System_SByte:
                    case SpecialType.System_Int16:
                    case SpecialType.System_UInt16:
                    case SpecialType.System_Char:
                    case SpecialType.System_UInt32:
                    case SpecialType.System_Int32:
                        return SpecialMember.System_IntPtr__op_Explicit_ToInt32;
                    case SpecialType.System_UInt64:
                    case SpecialType.System_Int64:
                    case SpecialType.System_Single:
                    case SpecialType.System_Double:
                    case SpecialType.System_Decimal:
                        return SpecialMember.System_IntPtr__op_Explicit_ToInt64;
                }
            }
            else if (s0Type == SpecialType.System_UIntPtr)
            {
                if (target.IsPointerOrFunctionPointer())
                {
                    return SpecialMember.System_UIntPtr__op_Explicit_ToPointer;
                }
 
                switch (t0Type)
                {
                    case SpecialType.System_SByte:
                    case SpecialType.System_Int16:
                    case SpecialType.System_Int32:
                    case SpecialType.System_Byte:
                    case SpecialType.System_UInt16:
                    case SpecialType.System_Char:
                    case SpecialType.System_UInt32:
                        return SpecialMember.System_UIntPtr__op_Explicit_ToUInt32;
                    case SpecialType.System_UInt64:
                    case SpecialType.System_Int64:
                    case SpecialType.System_Single:
                    case SpecialType.System_Double:
                    case SpecialType.System_Decimal:
                        return SpecialMember.System_UIntPtr__op_Explicit_ToUInt64;
                }
            }
 
            throw ExceptionUtilities.Unreachable();
        }
 
        // https://github.com/dotnet/roslyn/issues/42452: Test with native integers and expression trees.
        private static SpecialMember DecimalConversionMethod(TypeSymbol typeFrom, TypeSymbol typeTo)
        {
            if (typeFrom.SpecialType == SpecialType.System_Decimal)
            {
                // Rewrite Decimal to Numeric
                switch (typeTo.SpecialType)
                {
                    case SpecialType.System_Char: return SpecialMember.System_Decimal__op_Explicit_ToChar;
                    case SpecialType.System_SByte: return SpecialMember.System_Decimal__op_Explicit_ToSByte;
                    case SpecialType.System_Byte: return SpecialMember.System_Decimal__op_Explicit_ToByte;
                    case SpecialType.System_Int16: return SpecialMember.System_Decimal__op_Explicit_ToInt16;
                    case SpecialType.System_UInt16: return SpecialMember.System_Decimal__op_Explicit_ToUInt16;
                    case SpecialType.System_Int32: return SpecialMember.System_Decimal__op_Explicit_ToInt32;
                    case SpecialType.System_UInt32: return SpecialMember.System_Decimal__op_Explicit_ToUInt32;
                    case SpecialType.System_Int64: return SpecialMember.System_Decimal__op_Explicit_ToInt64;
                    case SpecialType.System_UInt64: return SpecialMember.System_Decimal__op_Explicit_ToUInt64;
                    case SpecialType.System_Single: return SpecialMember.System_Decimal__op_Explicit_ToSingle;
                    case SpecialType.System_Double: return SpecialMember.System_Decimal__op_Explicit_ToDouble;
                    default:
                        throw ExceptionUtilities.UnexpectedValue(typeTo.SpecialType);
                }
            }
            else
            {
                // Rewrite Numeric to Decimal
                switch (typeFrom.SpecialType)
                {
                    case SpecialType.System_Char: return SpecialMember.System_Decimal__op_Implicit_FromChar;
                    case SpecialType.System_SByte: return SpecialMember.System_Decimal__op_Implicit_FromSByte;
                    case SpecialType.System_Byte: return SpecialMember.System_Decimal__op_Implicit_FromByte;
                    case SpecialType.System_Int16: return SpecialMember.System_Decimal__op_Implicit_FromInt16;
                    case SpecialType.System_UInt16: return SpecialMember.System_Decimal__op_Implicit_FromUInt16;
                    case SpecialType.System_Int32: return SpecialMember.System_Decimal__op_Implicit_FromInt32;
                    case SpecialType.System_UInt32: return SpecialMember.System_Decimal__op_Implicit_FromUInt32;
                    case SpecialType.System_Int64: return SpecialMember.System_Decimal__op_Implicit_FromInt64;
                    case SpecialType.System_UInt64: return SpecialMember.System_Decimal__op_Implicit_FromUInt64;
                    case SpecialType.System_Single: return SpecialMember.System_Decimal__op_Explicit_FromSingle;
                    case SpecialType.System_Double: return SpecialMember.System_Decimal__op_Explicit_FromDouble;
                    default:
                        throw ExceptionUtilities.UnexpectedValue(typeFrom.SpecialType);
                }
            }
        }
 
        private BoundExpression RewriteDecimalConversion(SyntaxNode syntax, BoundExpression operand, TypeSymbol fromType, TypeSymbol toType, bool @checked, bool isImplicit, ConstantValue? constantValueOpt)
        {
            Debug.Assert(fromType.SpecialType == SpecialType.System_Decimal || toType.SpecialType == SpecialType.System_Decimal);
 
            if (fromType.SpecialType == SpecialType.System_Decimal)
            {
                switch (toType.SpecialType)
                {
                    case SpecialType.System_IntPtr:
                    case SpecialType.System_UIntPtr:
                        operand = RewriteDecimalConversionCore(syntax, operand, fromType, get64BitType(_compilation, signed: toType.SpecialType == SpecialType.System_IntPtr), isImplicit, constantValueOpt);
                        return MakeConversionNode(operand, toType, @checked);
                }
            }
            else
            {
                switch (fromType.SpecialType)
                {
                    case SpecialType.System_IntPtr:
                    case SpecialType.System_UIntPtr:
                        operand = MakeConversionNode(operand, get64BitType(_compilation, signed: fromType.SpecialType == SpecialType.System_IntPtr), @checked);
                        Debug.Assert(operand.Type is { });
                        return RewriteDecimalConversionCore(syntax, operand, operand.Type, toType, isImplicit, constantValueOpt);
                }
            }
 
            return RewriteDecimalConversionCore(syntax, operand, fromType, toType, isImplicit, constantValueOpt);
 
            static TypeSymbol get64BitType(CSharpCompilation compilation, bool signed) => compilation.GetSpecialType(signed ? SpecialType.System_Int64 : SpecialType.System_UInt64);
        }
 
        private BoundExpression RewriteDecimalConversionCore(SyntaxNode syntax, BoundExpression operand, TypeSymbol fromType, TypeSymbol toType, bool isImplicit, ConstantValue? constantValueOpt)
        {
            // call the method
            SpecialMember member = DecimalConversionMethod(fromType, toType);
            if (!TryGetSpecialTypeMethod(syntax, member, out MethodSymbol method))
            {
                return BadExpression(syntax, toType, operand);
            }
 
            if (_inExpressionLambda)
            {
                ConversionKind conversionKind = isImplicit ? ConversionKind.ImplicitUserDefined : ConversionKind.ExplicitUserDefined;
                var conversion = new Conversion(conversionKind, method, isExtensionMethod: false);
 
                return new BoundConversion(syntax, operand, conversion, @checked: false, explicitCastInCode: false, conversionGroupOpt: null, constantValueOpt: constantValueOpt, type: toType);
            }
            else
            {
                Debug.Assert(TypeSymbol.Equals(method.ReturnType, toType, TypeCompareKind.ConsiderEverything2));
                return BoundCall.Synthesized(syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, method, operand);
            }
        }
 
        /// <summary>
        /// Reports diagnostics and returns Conversion.NoConversion in case of missing runtime helpers.
        /// </summary>
        private Conversion TryMakeConversion(SyntaxNode syntax, Conversion conversion, TypeSymbol fromType, TypeSymbol toType, bool @checked)
        {
            switch (conversion.Kind)
            {
                case ConversionKind.ExplicitUserDefined:
                case ConversionKind.ImplicitUserDefined:
                    {
                        conversion.AssertUnderlyingConversionsChecked();
                        var meth = conversion.Method;
                        Debug.Assert(meth is { });
                        Conversion fromConversion = TryMakeConversion(syntax, conversion.UserDefinedFromConversion, fromType, meth.Parameters[0].Type, @checked: @checked);
                        if (!fromConversion.Exists)
                        {
                            return Conversion.NoConversion;
                        }
 
                        Conversion toConversion = TryMakeConversion(syntax, conversion.UserDefinedToConversion, meth.ReturnType, toType, @checked: @checked);
                        if (!toConversion.Exists)
                        {
                            return Conversion.NoConversion;
                        }
 
                        if (fromConversion == conversion.UserDefinedFromConversion && toConversion == conversion.UserDefinedToConversion)
                        {
                            return conversion;
                        }
                        else
                        {
                            // TODO: how do we distinguish from normal and lifted conversions here?
                            var analysis = UserDefinedConversionAnalysis.Normal(conversion.ConstrainedToTypeOpt, meth, fromConversion, toConversion, fromType, toType);
                            var result = UserDefinedConversionResult.Valid(ImmutableArray.Create<UserDefinedConversionAnalysis>(analysis), 0);
                            var resultConversion = new Conversion(result, conversion.IsImplicit);
                            resultConversion.MarkUnderlyingConversionsChecked();
                            return resultConversion;
                        }
                    }
                case ConversionKind.IntPtr:
                    {
                        Debug.Assert(!_compilation.Assembly.RuntimeSupportsNumericIntPtr);
                        SpecialMember member = GetIntPtrConversionMethod(fromType, toType);
                        MethodSymbol method;
                        if (!TryGetSpecialTypeMethod(syntax, member, out method))
                        {
                            return Conversion.NoConversion;
                        }
 
                        return TryMakeUserDefinedConversion(syntax, method, fromType, toType, @checked: @checked, conversion.IsImplicit);
                    }
                case ConversionKind.ImplicitNumeric:
                case ConversionKind.ExplicitNumeric:
                    // TODO: what about nullable?
                    if (fromType.SpecialType == SpecialType.System_Decimal || toType.SpecialType == SpecialType.System_Decimal)
                    {
                        SpecialMember member = DecimalConversionMethod(fromType, toType);
                        MethodSymbol method;
                        if (!TryGetSpecialTypeMethod(syntax, member, out method))
                        {
                            return Conversion.NoConversion;
                        }
 
                        return TryMakeUserDefinedConversion(syntax, method, fromType, toType, @checked: @checked, conversion.IsImplicit);
                    }
                    return conversion;
                case ConversionKind.ImplicitEnumeration:
                case ConversionKind.ExplicitEnumeration:
                    // TODO: what about nullable?
                    if (fromType.SpecialType == SpecialType.System_Decimal)
                    {
                        var underlying = toType.GetEnumUnderlyingType();
                        Debug.Assert(underlying is { });
                        SpecialMember member = DecimalConversionMethod(fromType, underlying);
                        MethodSymbol method;
                        if (!TryGetSpecialTypeMethod(syntax, member, out method))
                        {
                            return Conversion.NoConversion;
                        }
 
                        return TryMakeUserDefinedConversion(syntax, method, fromType, toType, @checked: @checked, conversion.IsImplicit);
                    }
                    else if (toType.SpecialType == SpecialType.System_Decimal)
                    {
                        var underlying = fromType.GetEnumUnderlyingType();
                        Debug.Assert(underlying is { });
                        SpecialMember member = DecimalConversionMethod(underlying, toType);
                        MethodSymbol method;
                        if (!TryGetSpecialTypeMethod(syntax, member, out method))
                        {
                            return Conversion.NoConversion;
                        }
 
                        return TryMakeUserDefinedConversion(syntax, method, fromType, toType, @checked: @checked, conversion.IsImplicit);
                    }
                    return conversion;
                default:
                    return conversion;
            }
        }
 
        /// <summary>
        /// Reports diagnostics and returns Conversion.NoConversion in case of missing runtime helpers.
        /// </summary>
        private Conversion TryMakeConversion(SyntaxNode syntax, TypeSymbol fromType, TypeSymbol toType, bool @checked)
        {
            CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo();
            var result = TryMakeConversion(syntax, _compilation.Conversions.ClassifyConversionFromType(fromType, toType, isChecked: @checked, ref useSiteInfo), fromType, toType, @checked: @checked);
            _diagnostics.Add(syntax, useSiteInfo);
            return result;
        }
 
        /// <summary>
        /// Reports diagnostics and returns Conversion.NoConversion in case of missing runtime helpers.
        /// </summary>
        private Conversion TryMakeUserDefinedConversion(SyntaxNode syntax, MethodSymbol meth, TypeSymbol fromType, TypeSymbol toType, bool @checked, bool isImplicit)
        {
            Debug.Assert(!meth.ContainingType.IsInterface);
 
            Conversion fromConversion = TryMakeConversion(syntax, fromType, meth.Parameters[0].Type, @checked: @checked);
            if (!fromConversion.Exists)
            {
                return Conversion.NoConversion;
            }
 
            Conversion toConversion = TryMakeConversion(syntax, meth.ReturnType, toType, @checked: @checked);
            if (!toConversion.Exists)
            {
                return Conversion.NoConversion;
            }
 
            // TODO: distinguish between normal and lifted conversions here
            var analysis = UserDefinedConversionAnalysis.Normal(constrainedToTypeOpt: null, meth, fromConversion, toConversion, fromType, toType);
            var result = UserDefinedConversionResult.Valid(ImmutableArray.Create<UserDefinedConversionAnalysis>(analysis), 0);
            return new Conversion(result, isImplicit);
        }
    }
}