File: Lowering\SyntheticBoundNodeFactory.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.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.CSharp.CodeGen;
using Microsoft.CodeAnalysis.CSharp.Emit;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    /// <summary>
    /// A helper class for synthesizing quantities of code.
    /// </summary>
    internal sealed class SyntheticBoundNodeFactory
    {
        /// <summary>
        /// Thrown by the bound node factory when there is a failure to synthesize code.
        /// An appropriate diagnostic is included that should be reported.  Currently
        /// the only diagnostic handled through this mechanism is a missing special/well-known
        /// member.
        /// </summary>
        public class MissingPredefinedMember : Exception
        {
            public MissingPredefinedMember(Diagnostic error) : base(error.ToString())
            {
                this.Diagnostic = error;
            }
 
            public Diagnostic Diagnostic { get; }
        }
 
        public CSharpCompilation Compilation { get { return CompilationState.Compilation; } }
        public SyntaxNode Syntax { get; set; }
        public PEModuleBuilder? ModuleBuilderOpt { get { return CompilationState.ModuleBuilderOpt; } }
        public BindingDiagnosticBag Diagnostics { get; }
        public InstrumentationState? InstrumentationState { get; }
        public TypeCompilationState CompilationState { get; }
 
        // Current enclosing type, or null if not available.
        private NamedTypeSymbol? _currentType;
        public NamedTypeSymbol? CurrentType
        {
            get { return _currentType; }
            set
            {
                _currentType = value;
                CheckCurrentType();
            }
        }
 
        // current method, possibly a lambda or local function, or null if not available
        private MethodSymbol? _currentFunction;
        public MethodSymbol? CurrentFunction
        {
            get { return _currentFunction; }
            set
            {
                _currentFunction = value;
                if (value is { } &&
                    value.MethodKind != MethodKind.AnonymousFunction &&
                    value.MethodKind != MethodKind.LocalFunction)
                {
                    _topLevelMethod = value;
                    _currentType = value.ContainingType;
                }
                CheckCurrentType();
            }
        }
 
        // The nearest enclosing non-lambda method, or null if not available
        private MethodSymbol? _topLevelMethod;
        public MethodSymbol? TopLevelMethod
        {
            get { return _topLevelMethod; }
            private set
            {
                _topLevelMethod = value;
                CheckCurrentType();
            }
        }
 
        /// <summary>
        /// Create a bound node factory. Note that the use of the factory to get special or well-known members
        /// that do not exist will result in an exception of type <see cref="MissingPredefinedMember"/> being thrown.
        /// </summary>
        /// <param name="topLevelMethod">The top-level method that will contain the code</param>
        /// <param name="node">The syntax node to which generated code should be attributed</param>
        /// <param name="compilationState">The state of compilation of the enclosing type</param>
        /// <param name="diagnostics">A bag where any diagnostics should be output</param>
        /// <param name="instrumentationState">Instrumentation state, if the factory is used for local lowering phase.</param>
        public SyntheticBoundNodeFactory(MethodSymbol topLevelMethod, SyntaxNode node, TypeCompilationState compilationState, BindingDiagnosticBag diagnostics, InstrumentationState? instrumentationState = null)
            : this(topLevelMethod, topLevelMethod.ContainingType, node, compilationState, diagnostics, instrumentationState)
        {
        }
 
        /// <param name="topLevelMethodOpt">The top-level method that will contain the code</param>
        /// <param name="currentClassOpt">The enclosing class</param>
        /// <param name="node">The syntax node to which generated code should be attributed</param>
        /// <param name="compilationState">The state of compilation of the enclosing type</param>
        /// <param name="diagnostics">A bag where any diagnostics should be output</param>
        /// <param name="instrumentationState">Instrumentation state, if the factory is used for local lowering phase.</param>
        public SyntheticBoundNodeFactory(MethodSymbol? topLevelMethodOpt, NamedTypeSymbol? currentClassOpt, SyntaxNode node, TypeCompilationState compilationState, BindingDiagnosticBag diagnostics, InstrumentationState? instrumentationState = null)
        {
            Debug.Assert(node != null);
            Debug.Assert(compilationState != null);
            Debug.Assert(diagnostics != null);
 
            this.CompilationState = compilationState;
            this.CurrentType = currentClassOpt;
            this.TopLevelMethod = topLevelMethodOpt;
            this.CurrentFunction = topLevelMethodOpt;
            this.Syntax = node;
            this.Diagnostics = diagnostics;
            this.InstrumentationState = instrumentationState;
        }
 
        [Conditional("DEBUG")]
        private void CheckCurrentType()
        {
            if (CurrentType is { })
            {
                Debug.Assert(TopLevelMethod is null || TypeSymbol.Equals(TopLevelMethod.ContainingType, CurrentType, TypeCompareKind.ConsiderEverything2));
 
                // In EE scenarios, lambdas and local functions are considered to be contained by the
                // user-defined methods, rather than the EE-defined methods for which we are generating
                // bound nodes. This is because the containing symbols are used to determine the type
                // of the "this" parameter, which we need to be the user-defined types.
                Debug.Assert(CurrentFunction is null ||
                    CurrentFunction.MethodKind == MethodKind.AnonymousFunction ||
                    CurrentFunction.MethodKind == MethodKind.LocalFunction ||
                    TypeSymbol.Equals(CurrentFunction.ContainingType, CurrentType, TypeCompareKind.ConsiderEverything2));
            }
        }
 
        public void AddNestedType(NamedTypeSymbol nestedType)
        {
            // It is only valid to call this on a bound node factory with a module builder.
            Debug.Assert(ModuleBuilderOpt is { });
            ModuleBuilderOpt.AddSynthesizedDefinition(CurrentType, nestedType.GetCciAdapter());
        }
 
        public void OpenNestedType(NamedTypeSymbol nestedType)
        {
            // TODO: we used to have an invariant that a bound node factory was tied to a
            // single enclosing class.  This breaks that.  It would be nice to reintroduce that
            // invariant.
            AddNestedType(nestedType);
            CurrentFunction = null;
            TopLevelMethod = null;
            CurrentType = nestedType;
        }
 
        public BoundHoistedFieldAccess HoistedField(FieldSymbol field)
        {
            return new BoundHoistedFieldAccess(Syntax, field, field.Type);
        }
 
        public StateMachineFieldSymbol StateMachineField(TypeWithAnnotations type, string name, bool isPublic = false, bool isThis = false)
        {
            Debug.Assert(CurrentType is { });
            var result = new StateMachineFieldSymbol(CurrentType, type, name, isPublic, isThis);
            AddField(CurrentType, result);
            return result;
        }
 
        public StateMachineFieldSymbol StateMachineField(TypeSymbol type, string name, bool isPublic = false, bool isThis = false)
        {
            Debug.Assert(CurrentType is { });
            var result = new StateMachineFieldSymbol(CurrentType, TypeWithAnnotations.Create(type), name, isPublic, isThis);
            AddField(CurrentType, result);
            return result;
        }
 
        public StateMachineFieldSymbol StateMachineField(TypeSymbol type, string name, SynthesizedLocalKind synthesizedKind, int slotIndex)
        {
            Debug.Assert(CurrentType is { });
            var result = new StateMachineFieldSymbol(CurrentType, type, name, synthesizedKind, slotIndex, isPublic: false);
            AddField(CurrentType, result);
            return result;
        }
 
        public StateMachineFieldSymbol StateMachineField(TypeSymbol type, string name, LocalSlotDebugInfo slotDebugInfo, int slotIndex)
        {
            Debug.Assert(CurrentType is { });
            var result = new StateMachineFieldSymbol(CurrentType, type, name, slotDebugInfo, slotIndex, isPublic: false);
            AddField(CurrentType, result);
            return result;
        }
 
        public void AddField(NamedTypeSymbol containingType, FieldSymbol field)
        {
            // It is only valid to call this on a bound node factory with a module builder.
            Debug.Assert(ModuleBuilderOpt is { });
            ModuleBuilderOpt.AddSynthesizedDefinition(containingType, field.GetCciAdapter());
        }
 
        public GeneratedLabelSymbol GenerateLabel(string prefix)
        {
            return new GeneratedLabelSymbol(prefix);
        }
 
        public BoundThisReference This()
        {
            Debug.Assert(CurrentFunction is { IsStatic: false });
            return new BoundThisReference(Syntax, CurrentFunction.ThisParameter.Type) { WasCompilerGenerated = true };
        }
 
        public BoundExpression This(LocalSymbol thisTempOpt)
        {
            return (thisTempOpt != null) ? Local(thisTempOpt) : (BoundExpression)This();
        }
 
        public BoundBaseReference Base(NamedTypeSymbol baseType)
        {
            Debug.Assert(CurrentFunction is { IsStatic: false });
            return new BoundBaseReference(Syntax, baseType) { WasCompilerGenerated = true };
        }
 
        public BoundBadExpression BadExpression(TypeSymbol type)
        {
            return new BoundBadExpression(Syntax, LookupResultKind.Empty, ImmutableArray<Symbol?>.Empty, ImmutableArray<BoundExpression>.Empty, type, hasErrors: true);
        }
 
        public BoundParameter Parameter(ParameterSymbol p)
        {
            return new BoundParameter(Syntax, p, p.Type) { WasCompilerGenerated = true };
        }
 
        public BoundFieldAccess Field(BoundExpression? receiver, FieldSymbol f)
        {
            return new BoundFieldAccess(Syntax, receiver, f, ConstantValue.NotAvailable, LookupResultKind.Viable, f.Type) { WasCompilerGenerated = true };
        }
 
        public BoundFieldAccess InstanceField(FieldSymbol f)
        {
            return this.Field(this.This(), f);
        }
 
        public BoundExpression Property(WellKnownMember member)
        {
            return Property(null, member);
        }
 
        public BoundExpression Property(BoundExpression? receiverOpt, WellKnownMember member)
        {
            var propertySym = (PropertySymbol)WellKnownMember(member);
            Debug.Assert(receiverOpt is null || receiverOpt.Type is { } &&
                receiverOpt.Type.GetMembers(propertySym.Name).OfType<PropertySymbol>().Single() == propertySym);
            Binder.ReportUseSite(propertySym, Diagnostics, Syntax);
            return Property(receiverOpt, propertySym);
        }
 
        public BoundExpression Property(BoundExpression? receiverOpt, PropertySymbol property)
        {
            Debug.Assert((receiverOpt is null) == property.IsStatic);
 
            // check for System.Array.[Length|LongLength] on a single dimensional array,
            // we have a special node for such cases.
            Debug.Assert(!(receiverOpt is { Type: ArrayTypeSymbol { IsSZArray: true } } &&
                           (ReferenceEquals(property, Compilation.GetSpecialTypeMember(CodeAnalysis.SpecialMember.System_Array__Length)) ||
                            ReferenceEquals(property, Compilation.GetSpecialTypeMember(CodeAnalysis.SpecialMember.System_Array__LongLength)))), "Use BoundArrayLength instead?");
 
            var accessor = property.GetOwnOrInheritedGetMethod();
            Debug.Assert(accessor is not null);
            return Call(receiverOpt, accessor);
        }
 
        public BoundExpression Indexer(BoundExpression? receiverOpt, PropertySymbol property, BoundExpression arg0)
        {
            Debug.Assert((receiverOpt is null) == property.IsStatic);
            var accessor = property.GetOwnOrInheritedGetMethod();
            Debug.Assert(accessor is not null);
            return Call(receiverOpt, accessor, arg0);
        }
 
        public NamedTypeSymbol SpecialType(SpecialType st)
        {
            NamedTypeSymbol specialType = Compilation.GetSpecialType(st);
            Binder.ReportUseSite(specialType, Diagnostics, Syntax);
            return specialType;
        }
 
        public ArrayTypeSymbol WellKnownArrayType(WellKnownType elementType)
        {
            return Compilation.CreateArrayTypeSymbol(WellKnownType(elementType));
        }
 
        public NamedTypeSymbol WellKnownType(WellKnownType wt)
        {
            NamedTypeSymbol wellKnownType = Compilation.GetWellKnownType(wt);
            Binder.ReportUseSite(wellKnownType, Diagnostics, Syntax);
            return wellKnownType;
        }
 
        /// <summary>
        /// Get the symbol for a well-known member. The use of this method to get a well-known member
        /// that does not exist will result in an exception of type <see cref="MissingPredefinedMember"/> being thrown
        /// containing an appropriate diagnostic for the caller to report.
        /// </summary>
        /// <param name="wm">The desired well-known member</param>
        /// <param name="isOptional">If true, the method may return null for a missing member without an exception</param>
        /// <returns>A symbol for the well-known member, or null if it is missing and <paramref name="isOptional"/> == true</returns>
        public Symbol? WellKnownMember(WellKnownMember wm, bool isOptional)
        {
            Symbol? wellKnownMember = Binder.GetWellKnownTypeMember(Compilation, wm, Diagnostics, syntax: Syntax, isOptional: true);
            if (wellKnownMember is null && !isOptional)
            {
                RuntimeMembers.MemberDescriptor memberDescriptor = WellKnownMembers.GetDescriptor(wm);
                var diagnostic = new CSDiagnostic(new CSDiagnosticInfo(ErrorCode.ERR_MissingPredefinedMember, memberDescriptor.DeclaringTypeMetadataName, memberDescriptor.Name), Syntax.Location);
                throw new MissingPredefinedMember(diagnostic);
            }
 
            return wellKnownMember;
        }
 
        public Symbol WellKnownMember(WellKnownMember wm)
        {
            return WellKnownMember(wm, false)!;
        }
 
        public MethodSymbol? WellKnownMethod(WellKnownMember wm, bool isOptional)
        {
            return (MethodSymbol?)WellKnownMember(wm, isOptional);
        }
 
        public MethodSymbol WellKnownMethod(WellKnownMember wm)
        {
            return (MethodSymbol)WellKnownMember(wm, isOptional: false)!;
        }
 
        /// <summary>
        /// Get the symbol for a special member. The use of this method to get a special member
        /// that does not exist will result in an exception of type MissingPredefinedMember being thrown
        /// containing an appropriate diagnostic for the caller to report.
        /// </summary>
        /// <param name="sm">The desired special member</param>
        /// <returns>A symbol for the special member.</returns>
        public Symbol SpecialMember(SpecialMember sm)
        {
            var result = SpecialMember(sm, isOptional: false);
            Debug.Assert(result is not null);
            return result;
        }
 
        public Symbol? SpecialMember(SpecialMember sm, bool isOptional = false)
        {
            Symbol specialMember = Compilation.GetSpecialTypeMember(sm);
            if (specialMember is null)
            {
                if (isOptional)
                {
                    return null;
                }
 
                RuntimeMembers.MemberDescriptor memberDescriptor = SpecialMembers.GetDescriptor(sm);
                var diagnostic = new CSDiagnostic(new CSDiagnosticInfo(ErrorCode.ERR_MissingPredefinedMember, memberDescriptor.DeclaringTypeMetadataName, memberDescriptor.Name), Syntax.Location);
                throw new MissingPredefinedMember(diagnostic);
            }
 
            UseSiteInfo<AssemblySymbol> useSiteInfo = specialMember.GetUseSiteInfo();
 
            if (isOptional)
            {
                if (useSiteInfo.DiagnosticInfo?.DefaultSeverity == DiagnosticSeverity.Error)
                {
                    return null;
                }
 
                // Not interested in warnings
            }
            else
            {
                Diagnostics.Add(useSiteInfo, Syntax);
            }
 
            return specialMember;
        }
 
        public MethodSymbol SpecialMethod(SpecialMember sm)
        {
            var result = (MethodSymbol?)SpecialMember(sm, isOptional: false);
            Debug.Assert(result is not null);
            return result;
        }
 
        public MethodSymbol? SpecialMethod(SpecialMember sm, bool isOptional)
        {
            return (MethodSymbol?)SpecialMember(sm, isOptional);
        }
 
        public PropertySymbol SpecialProperty(SpecialMember sm)
        {
            return (PropertySymbol)SpecialMember(sm);
        }
 
        public BoundExpressionStatement Assignment(BoundExpression left, BoundExpression right, bool isRef = false)
        {
            return ExpressionStatement(AssignmentExpression(left, right, isRef));
        }
 
        public BoundExpressionStatement ExpressionStatement(BoundExpression expr)
        {
            return new BoundExpressionStatement(Syntax, expr) { WasCompilerGenerated = true };
        }
 
        /// <summary>
        /// Creates a general assignment that might be instrumented.
        /// </summary>
        public BoundExpression AssignmentExpression(BoundExpression left, BoundExpression right, bool isRef = false)
        {
            return AssignmentExpression(Syntax, left, right, isRef: isRef, wasCompilerGenerated: true);
        }
 
        /// <summary>
        /// Creates a general assignment that might be instrumented.
        /// </summary>
        public BoundExpression AssignmentExpression(SyntaxNode syntax, BoundExpression left, BoundExpression right, bool isRef = false, bool hasErrors = false, bool wasCompilerGenerated = false)
        {
            Debug.Assert(left.Type is { } && right.Type is { } &&
                (left.Type.Equals(right.Type, TypeCompareKind.AllIgnoreOptions) ||
                 StackOptimizerPass1.IsFixedBufferAssignmentToRefLocal(left, right, isRef) ||
                 right.Type.IsErrorType() || left.Type.IsErrorType()));
 
            var assignment = new BoundAssignmentOperator(syntax, left, right, isRef, left.Type, hasErrors) { WasCompilerGenerated = wasCompilerGenerated };
 
            return (InstrumentationState?.IsSuppressed == false && left is BoundLocal { LocalSymbol.SynthesizedKind: SynthesizedLocalKind.UserDefined } or BoundParameter) ?
                InstrumentationState.Instrumenter.InstrumentUserDefinedLocalAssignment(assignment) :
                assignment;
        }
 
        public BoundBlock Block()
        {
            return Block(ImmutableArray<BoundStatement>.Empty);
        }
 
        public BoundBlock Block(ImmutableArray<BoundStatement> statements)
        {
            return Block(ImmutableArray<LocalSymbol>.Empty, statements);
        }
 
        public BoundBlock Block(params BoundStatement[] statements)
        {
            return Block(ImmutableArray.Create(statements));
        }
 
        public BoundBlock Block(ImmutableArray<LocalSymbol> locals, params BoundStatement[] statements)
        {
            return Block(locals, ImmutableArray.Create(statements));
        }
 
        public BoundBlock Block(ImmutableArray<LocalSymbol> locals, ImmutableArray<BoundStatement> statements)
        {
            return new BoundBlock(Syntax, locals, statements) { WasCompilerGenerated = true };
        }
 
        public BoundBlock Block(ImmutableArray<LocalSymbol> locals, ImmutableArray<LocalFunctionSymbol> localFunctions, params BoundStatement[] statements)
        {
            return Block(locals, localFunctions, ImmutableArray.Create(statements));
        }
 
        public BoundBlock Block(ImmutableArray<LocalSymbol> locals, ImmutableArray<LocalFunctionSymbol> localFunctions, ImmutableArray<BoundStatement> statements)
        {
            return new BoundBlock(Syntax, locals, localFunctions, hasUnsafeModifier: false, instrumentation: null, statements) { WasCompilerGenerated = true };
        }
 
        public BoundExtractedFinallyBlock ExtractedFinallyBlock(BoundBlock finallyBlock)
        {
            return new BoundExtractedFinallyBlock(Syntax, finallyBlock) { WasCompilerGenerated = true };
        }
 
        public BoundStatementList StatementList()
        {
            return StatementList(ImmutableArray<BoundStatement>.Empty);
        }
 
        public BoundStatementList StatementList(ImmutableArray<BoundStatement> statements)
        {
            return new BoundStatementList(Syntax, statements) { WasCompilerGenerated = true };
        }
 
        public BoundStatementList StatementList(BoundStatement first, BoundStatement second)
        {
            return new BoundStatementList(Syntax, ImmutableArray.Create(first, second)) { WasCompilerGenerated = true };
        }
 
        [return: NotNullIfNotNull(nameof(first)), NotNullIfNotNull(nameof(second))]
        public BoundStatement? Concat(BoundStatement? first, BoundStatement? second)
            => (first == null) ? second : (second == null) ? first : StatementList(first, second);
 
        public BoundBlockInstrumentation CombineInstrumentation(BoundBlockInstrumentation? innerInstrumentation = null, LocalSymbol? local = null, BoundStatement? prologue = null, BoundStatement? epilogue = null)
        {
            return (innerInstrumentation != null)
                ? new BoundBlockInstrumentation(
                    innerInstrumentation.Syntax,
                    (local != null) ? innerInstrumentation.Locals.Add(local) : innerInstrumentation.Locals,
                    (prologue != null) ? Concat(prologue, innerInstrumentation.Prologue) : innerInstrumentation.Prologue,
                    (epilogue != null) ? Concat(innerInstrumentation.Epilogue, epilogue) : innerInstrumentation.Epilogue)
                : new BoundBlockInstrumentation(
                    Syntax,
                    OneOrMany.OneOrNone(local),
                    prologue,
                    epilogue);
        }
 
        public BoundStatement Instrument(BoundStatement statement, BoundBlockInstrumentation? instrumentation)
        {
            if (instrumentation == null)
            {
                return statement;
            }
 
            var statements = new TemporaryArray<BoundStatement>();
 
            if (instrumentation.Prologue != null)
            {
                statements.Add(instrumentation.Prologue);
            }
 
            if (instrumentation.Epilogue != null)
            {
                statements.Add(Try(Block(statement), ImmutableArray<BoundCatchBlock>.Empty, Block(instrumentation.Epilogue)));
            }
            else
            {
                statements.Add(statement);
            }
 
            return Block(instrumentation.Locals.ToImmutable(), statements.ToImmutableAndClear());
        }
 
        public BoundReturnStatement Return(BoundExpression? expression = null)
        {
            Debug.Assert(CurrentFunction is { });
 
            if (expression != null)
            {
                // If necessary, add a conversion on the return expression.
                var useSiteInfo =
#if DEBUG
                    CompoundUseSiteInfo<AssemblySymbol>.DiscardedDependencies;
#else
                    CompoundUseSiteInfo<AssemblySymbol>.Discarded;
#endif 
                var conversion = Compilation.Conversions.ClassifyConversionFromType(expression.Type, CurrentFunction.ReturnType, isChecked: false, ref useSiteInfo);
                Debug.Assert(useSiteInfo.Diagnostics.IsNullOrEmpty());
                Debug.Assert(conversion.Kind != ConversionKind.NoConversion);
                if (conversion.Kind != ConversionKind.Identity)
                {
                    Debug.Assert(CurrentFunction.RefKind == RefKind.None);
                    expression = BoundConversion.Synthesized(Syntax, expression, conversion, false, explicitCastInCode: false, conversionGroupOpt: null, ConstantValue.NotAvailable, CurrentFunction.ReturnType);
                }
            }
 
            return new BoundReturnStatement(Syntax, CurrentFunction.RefKind, expression, @checked: false) { WasCompilerGenerated = true };
        }
 
        public void CloseMethod(BoundStatement body)
        {
            Debug.Assert(CurrentFunction is { });
            if (body.Kind != BoundKind.Block)
            {
                body = Block(body);
            }
 
            CompilationState.AddSynthesizedMethod(CurrentFunction, body);
            CurrentFunction = null;
        }
 
        public LocalSymbol SynthesizedLocal(
            TypeSymbol type,
            SyntaxNode? syntax = null,
            bool isPinned = false,
            bool isKnownToReferToTempIfReferenceType = false,
            RefKind refKind = RefKind.None,
            SynthesizedLocalKind kind = SynthesizedLocalKind.LoweringTemp
#if DEBUG
            ,
            [CallerLineNumber] int createdAtLineNumber = 0,
            [CallerFilePath] string createdAtFilePath = ""
#endif
            )
        {
            return new SynthesizedLocal(CurrentFunction, TypeWithAnnotations.Create(type), kind, syntax, isPinned,
                isKnownToReferToTempIfReferenceType, refKind
#if DEBUG
                , createdAtLineNumber, createdAtFilePath
#endif
                );
        }
 
        public LocalSymbol InterpolatedStringHandlerLocal(
            TypeSymbol type,
            SyntaxNode syntax
#if DEBUG
            ,
            [CallerLineNumber] int createdAtLineNumber = 0,
            [CallerFilePath] string createdAtFilePath = ""
#endif
            )
        {
            return new SynthesizedLocal(
                CurrentFunction,
                TypeWithAnnotations.Create(type),
                SynthesizedLocalKind.LoweringTemp,
                syntax
#if DEBUG
                , createdAtLineNumber: createdAtLineNumber, createdAtFilePath: createdAtFilePath
#endif
                );
        }
 
        public ParameterSymbol SynthesizedParameter(TypeSymbol type, string name, MethodSymbol? container = null, int ordinal = 0)
        {
            return SynthesizedParameterSymbol.Create(container, TypeWithAnnotations.Create(type), ordinal, RefKind.None, name);
        }
 
        public BoundBinaryOperator Binary(BinaryOperatorKind kind, TypeSymbol type, BoundExpression left, BoundExpression right)
        {
            return new BoundBinaryOperator(this.Syntax, kind, ConstantValue.NotAvailable, methodOpt: null, constrainedToTypeOpt: null, LookupResultKind.Viable, left, right, type) { WasCompilerGenerated = true };
        }
 
        public BoundAsOperator As(BoundExpression operand, TypeSymbol type)
        {
            return new BoundAsOperator(this.Syntax, operand, Type(type), operandPlaceholder: null, operandConversion: null, type) { WasCompilerGenerated = true };
        }
 
        public BoundIsOperator Is(BoundExpression operand, TypeSymbol type)
        {
            var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
            // Because compiler-generated nodes are not lowered, this conversion is not used later in the compiler.
            // But it is a required part of the `BoundIsOperator` node, so we compute a conversion here.
            Conversion c = Compilation.Conversions.ClassifyBuiltInConversion(operand.Type, type, isChecked: false, ref discardedUseSiteInfo);
            return new BoundIsOperator(this.Syntax, operand, Type(type), c.Kind, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Boolean)) { WasCompilerGenerated = true };
        }
 
        public BoundBinaryOperator LogicalAnd(BoundExpression left, BoundExpression right)
        {
            Debug.Assert(left.Type?.SpecialType == CodeAnalysis.SpecialType.System_Boolean);
            Debug.Assert(right.Type?.SpecialType == CodeAnalysis.SpecialType.System_Boolean);
            return Binary(BinaryOperatorKind.LogicalBoolAnd, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Boolean), left, right);
        }
 
        public BoundBinaryOperator LogicalOr(BoundExpression left, BoundExpression right)
        {
            Debug.Assert(left.Type?.SpecialType == CodeAnalysis.SpecialType.System_Boolean);
            Debug.Assert(right.Type?.SpecialType == CodeAnalysis.SpecialType.System_Boolean);
            return Binary(BinaryOperatorKind.LogicalBoolOr, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Boolean), left, right);
        }
 
        public BoundBinaryOperator IntEqual(BoundExpression left, BoundExpression right)
        {
            return Binary(BinaryOperatorKind.IntEqual, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Boolean), left, right);
        }
 
        public BoundBinaryOperator ObjectEqual(BoundExpression left, BoundExpression right)
        {
            return Binary(BinaryOperatorKind.ObjectEqual, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Boolean), left, right);
        }
 
        public BoundExpression IsNotNullReference(BoundExpression value)
        {
            var objectType = SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Object);
            return ObjectNotEqual(Convert(objectType, value, allowBoxingByRefLikeTypeParametersToObject: true), Null(objectType));
        }
 
        public BoundBinaryOperator ObjectNotEqual(BoundExpression left, BoundExpression right)
        {
            return Binary(BinaryOperatorKind.ObjectNotEqual, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Boolean), left, right);
        }
 
        public BoundBinaryOperator IntNotEqual(BoundExpression left, BoundExpression right)
        {
            return Binary(BinaryOperatorKind.IntNotEqual, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Boolean), left, right);
        }
 
        public BoundBinaryOperator IntLessThan(BoundExpression left, BoundExpression right)
        {
            return Binary(BinaryOperatorKind.IntLessThan, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Boolean), left, right);
        }
 
        public BoundBinaryOperator IntGreaterThanOrEqual(BoundExpression left, BoundExpression right)
        {
            return Binary(BinaryOperatorKind.IntGreaterThanOrEqual, SpecialType(CodeAnalysis.SpecialType.System_Boolean), left, right);
        }
 
        public BoundBinaryOperator IntSubtract(BoundExpression left, BoundExpression right)
        {
            return Binary(BinaryOperatorKind.IntSubtraction, SpecialType(CodeAnalysis.SpecialType.System_Int32), left, right);
        }
 
        public BoundBinaryOperator IntMultiply(BoundExpression left, BoundExpression right)
        {
            return Binary(BinaryOperatorKind.IntMultiplication, SpecialType(CodeAnalysis.SpecialType.System_Int32), left, right);
        }
 
        public BoundLiteral Literal(byte value)
        {
            return new BoundLiteral(Syntax, ConstantValue.Create(value), SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Byte)) { WasCompilerGenerated = true };
        }
 
        public BoundLiteral Literal(int value)
        {
            return new BoundLiteral(Syntax, ConstantValue.Create(value), SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Int32)) { WasCompilerGenerated = true };
        }
 
        public BoundLiteral Literal(StateMachineState value)
            => Literal((int)value);
 
        public BoundLiteral Literal(uint value)
        {
            return new BoundLiteral(Syntax, ConstantValue.Create(value), SpecialType(Microsoft.CodeAnalysis.SpecialType.System_UInt32)) { WasCompilerGenerated = true };
        }
 
        public BoundLiteral Literal(ConstantValue value, TypeSymbol type)
        {
            return new BoundLiteral(Syntax, value, type) { WasCompilerGenerated = true };
        }
 
        public BoundObjectCreationExpression New(NamedTypeSymbol type, params BoundExpression[] args)
        {
            var ctor = type.InstanceConstructors.Single(c => c.ParameterCount == args.Length);
            return New(ctor, args);
        }
 
        public BoundObjectCreationExpression New(MethodSymbol ctor, params BoundExpression[] args)
            => New(ctor, args.ToImmutableArray());
 
        public BoundObjectCreationExpression New(NamedTypeSymbol type, ImmutableArray<BoundExpression> args)
        {
            var ctor = type.InstanceConstructors.Single(c => c.ParameterCount == args.Length);
            return New(ctor, args);
        }
 
        public BoundObjectCreationExpression New(MethodSymbol ctor, ImmutableArray<BoundExpression> args)
            => new BoundObjectCreationExpression(Syntax, ctor, args) { WasCompilerGenerated = true };
 
        public BoundObjectCreationExpression New(MethodSymbol constructor, ImmutableArray<BoundExpression> arguments, ImmutableArray<RefKind> argumentRefKinds)
            => new BoundObjectCreationExpression(
                Syntax,
                constructor,
                arguments,
                argumentNamesOpt: default,
                argumentRefKinds,
                expanded: false,
                argsToParamsOpt: default,
                defaultArguments: default,
                constantValueOpt: null,
                initializerExpressionOpt: null,
                constructor.ContainingType)
            { WasCompilerGenerated = true };
 
        public BoundObjectCreationExpression New(WellKnownMember wm, ImmutableArray<BoundExpression> args)
        {
            var ctor = WellKnownMethod(wm);
            return new BoundObjectCreationExpression(Syntax, ctor, args) { WasCompilerGenerated = true };
        }
 
        public BoundExpression MakeIsNotANumberTest(BoundExpression input)
        {
            switch (input.Type)
            {
                case { SpecialType: CodeAnalysis.SpecialType.System_Double }:
                    // produce double.IsNaN(input)
                    return StaticCall(CodeAnalysis.SpecialMember.System_Double__IsNaN, input);
                case { SpecialType: CodeAnalysis.SpecialType.System_Single }:
                    // produce float.IsNaN(input)
                    return StaticCall(CodeAnalysis.SpecialMember.System_Single__IsNaN, input);
                default:
                    throw ExceptionUtilities.UnexpectedValue(input.Type);
            }
        }
 
        public BoundExpression StaticCall(TypeSymbol receiver, MethodSymbol method, params BoundExpression[] args)
        {
            if (method is null)
            {
                return new BoundBadExpression(Syntax, default(LookupResultKind), ImmutableArray<Symbol?>.Empty, args.AsImmutable(), receiver);
            }
 
            return Call(null, method, args);
        }
 
        public BoundExpression StaticCall(MethodSymbol method, ImmutableArray<BoundExpression> args)
            => Call(null, method, args);
 
        public BoundExpression StaticCall(WellKnownMember method, params BoundExpression[] args)
        {
            MethodSymbol methodSymbol = WellKnownMethod(method);
            Binder.ReportUseSite(methodSymbol, Diagnostics, Syntax);
            Debug.Assert(methodSymbol.IsStatic);
            return Call(null, methodSymbol, args);
        }
 
        public BoundExpression StaticCall(WellKnownMember method, ImmutableArray<TypeSymbol> typeArgs, params BoundExpression[] args)
        {
            MethodSymbol methodSymbol = WellKnownMethod(method);
            Binder.ReportUseSite(methodSymbol, Diagnostics, Syntax);
            Debug.Assert(methodSymbol.IsStatic);
            Debug.Assert(methodSymbol.IsGenericMethod);
            Debug.Assert(methodSymbol.Arity == typeArgs.Length);
 
            return Call(null, methodSymbol.Construct(typeArgs), args);
        }
 
        public BoundExpression StaticCall(SpecialMember method, params BoundExpression[] args)
        {
            MethodSymbol methodSymbol = SpecialMethod(method);
            Debug.Assert(methodSymbol.IsStatic);
            return Call(null, methodSymbol, args);
        }
 
        public BoundCall Call(BoundExpression? receiver, MethodSymbol method)
        {
            return Call(receiver, method, ImmutableArray<BoundExpression>.Empty);
        }
 
        public BoundCall Call(BoundExpression? receiver, MethodSymbol method, BoundExpression arg0, bool useStrictArgumentRefKinds = false)
        {
            return Call(receiver, method, ImmutableArray.Create(arg0), useStrictArgumentRefKinds);
        }
 
        public BoundCall Call(BoundExpression? receiver, MethodSymbol method, BoundExpression arg0, BoundExpression arg1, bool useStrictArgumentRefKinds = false)
        {
            return Call(receiver, method, ImmutableArray.Create(arg0, arg1), useStrictArgumentRefKinds);
        }
 
        public BoundCall Call(BoundExpression? receiver, MethodSymbol method, params BoundExpression[] args)
        {
            return Call(receiver, method, ImmutableArray.Create<BoundExpression>(args));
        }
 
        public BoundCall Call(BoundExpression? receiver, WellKnownMember method, BoundExpression arg0)
            => Call(receiver, WellKnownMethod(method), ImmutableArray.Create(arg0));
 
        public BoundCall Call(BoundExpression? receiver, MethodSymbol method, ImmutableArray<BoundExpression> args, bool useStrictArgumentRefKinds = false)
        {
            Debug.Assert(method.ParameterCount == args.Length);
 
            return new BoundCall(
                Syntax, receiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, method, args,
                argumentNamesOpt: default(ImmutableArray<string?>), argumentRefKindsOpt: getArgumentRefKinds(method, useStrictArgumentRefKinds), isDelegateCall: false, expanded: false,
                invokedAsExtensionMethod: false, argsToParamsOpt: default(ImmutableArray<int>), defaultArguments: default(BitVector), resultKind: LookupResultKind.Viable,
                type: method.ReturnType, hasErrors: method.OriginalDefinition is ErrorMethodSymbol)
            { WasCompilerGenerated = true };
 
            static ImmutableArray<RefKind> getArgumentRefKinds(MethodSymbol method, bool useStrictArgumentRefKinds)
            {
                var result = method.ParameterRefKinds;
 
                if (!result.IsDefaultOrEmpty && (result.Contains(RefKind.RefReadOnlyParameter) ||
                    (useStrictArgumentRefKinds && result.Contains(RefKind.In))))
                {
                    var builder = ArrayBuilder<RefKind>.GetInstance(result.Length);
 
                    foreach (var refKind in result)
                    {
                        builder.Add(refKind switch
                        {
                            RefKind.In or RefKind.RefReadOnlyParameter when useStrictArgumentRefKinds => RefKindExtensions.StrictIn,
                            RefKind.RefReadOnlyParameter => RefKind.In,
                            _ => refKind
                        });
                    }
 
                    return builder.ToImmutableAndFree();
                }
 
                return result;
            }
        }
 
        public BoundCall Call(BoundExpression? receiver, MethodSymbol method, ImmutableArray<RefKind> refKinds, ImmutableArray<BoundExpression> args)
        {
            Debug.Assert(method.ParameterCount == args.Length);
            return new BoundCall(
                Syntax, receiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, method, args,
                argumentNamesOpt: default(ImmutableArray<String?>), argumentRefKindsOpt: refKinds, isDelegateCall: false, expanded: false, invokedAsExtensionMethod: false,
                argsToParamsOpt: ImmutableArray<int>.Empty, defaultArguments: default(BitVector), resultKind: LookupResultKind.Viable, type: method.ReturnType)
            { WasCompilerGenerated = true };
        }
 
        public BoundExpression Conditional(BoundExpression condition, BoundExpression consequence, BoundExpression alternative, TypeSymbol type, bool isRef = false)
        {
            return new BoundConditionalOperator(Syntax, isRef, condition, consequence, alternative, constantValueOpt: null, type, wasTargetTyped: false, type) { WasCompilerGenerated = true };
        }
 
        public BoundComplexConditionalReceiver ComplexConditionalReceiver(BoundExpression valueTypeReceiver, BoundExpression referenceTypeReceiver)
        {
            Debug.Assert(valueTypeReceiver.Type is { });
            Debug.Assert(TypeSymbol.Equals(valueTypeReceiver.Type, referenceTypeReceiver.Type, TypeCompareKind.ConsiderEverything2));
            return new BoundComplexConditionalReceiver(Syntax, valueTypeReceiver, referenceTypeReceiver, valueTypeReceiver.Type) { WasCompilerGenerated = true };
        }
 
        public BoundExpression Coalesce(BoundExpression left, BoundExpression right)
        {
            Debug.Assert(left.Type!.Equals(right.Type, TypeCompareKind.IgnoreCustomModifiersAndArraySizesAndLowerBounds | TypeCompareKind.IgnoreNullableModifiersForReferenceTypes) || left.Type.IsErrorType());
            Debug.Assert(left.Type.IsReferenceType);
 
            return new BoundNullCoalescingOperator(Syntax, left, right, leftPlaceholder: null, leftConversion: null, BoundNullCoalescingOperatorResultKind.LeftType, @checked: false, left.Type) { WasCompilerGenerated = true };
        }
 
        public BoundStatement If(BoundExpression condition, BoundStatement thenClause, BoundStatement? elseClauseOpt = null)
        {
            return If(condition, ImmutableArray<LocalSymbol>.Empty, thenClause, elseClauseOpt);
        }
 
        public BoundStatement ConditionalGoto(BoundExpression condition, LabelSymbol label, bool jumpIfTrue)
        {
            return new BoundConditionalGoto(Syntax, condition, jumpIfTrue, label) { WasCompilerGenerated = true };
        }
 
        public BoundStatement If(BoundExpression condition, ImmutableArray<LocalSymbol> locals, BoundStatement thenClause, BoundStatement? elseClauseOpt = null)
        {
            // We translate
            //    if (condition) thenClause else elseClause
            // as
            //    {
            //       ConditionalGoto(!condition) alternative
            //       thenClause
            //       goto afterif;
            //       alternative:
            //       elseClause
            //       afterif:
            //    }
            Debug.Assert(thenClause != null);
 
            var statements = ArrayBuilder<BoundStatement>.GetInstance();
            var afterif = new GeneratedLabelSymbol("afterif");
 
            if (elseClauseOpt != null)
            {
                var alt = new GeneratedLabelSymbol("alternative");
 
                statements.Add(ConditionalGoto(condition, alt, false));
                statements.Add(thenClause);
                statements.Add(Goto(afterif));
                if (!locals.IsDefaultOrEmpty)
                {
                    var firstPart = this.Block(locals, statements.ToImmutable());
                    statements.Clear();
                    statements.Add(firstPart);
                }
 
                statements.Add(Label(alt));
                statements.Add(elseClauseOpt);
            }
            else
            {
                statements.Add(ConditionalGoto(condition, afterif, false));
                statements.Add(thenClause);
                if (!locals.IsDefaultOrEmpty)
                {
                    var firstPart = this.Block(locals, statements.ToImmutable());
                    statements.Clear();
                    statements.Add(firstPart);
                }
            }
 
            statements.Add(Label(afterif));
            return Block(statements.ToImmutableAndFree());
        }
 
        public BoundThrowStatement Throw(BoundExpression e)
        {
            return new BoundThrowStatement(Syntax, e) { WasCompilerGenerated = true };
        }
 
        public BoundLocal Local(LocalSymbol local)
        {
            return new BoundLocal(Syntax, local, null, local.Type) { WasCompilerGenerated = true };
        }
 
        public BoundExpression MakeSequence(LocalSymbol temp, params BoundExpression[] parts)
        {
            return MakeSequence(ImmutableArray.Create<LocalSymbol>(temp), parts);
        }
 
        public BoundExpression MakeSequence(params BoundExpression[] parts)
        {
            return MakeSequence(ImmutableArray<LocalSymbol>.Empty, parts);
        }
 
        public BoundExpression MakeSequence(ImmutableArray<LocalSymbol> locals, params BoundExpression[] parts)
        {
            var builder = ArrayBuilder<BoundExpression>.GetInstance();
            for (int i = 0; i < parts.Length - 1; i++)
            {
                var part = parts[i];
                if (LocalRewriter.ReadIsSideeffecting(part))
                {
                    builder.Add(parts[i]);
                }
            }
            var lastExpression = parts[parts.Length - 1];
 
            if (locals.IsDefaultOrEmpty && builder.Count == 0)
            {
                builder.Free();
                return lastExpression;
            }
 
            return Sequence(locals, builder.ToImmutableAndFree(), lastExpression);
        }
 
        public BoundSequence Sequence(BoundExpression[] sideEffects, BoundExpression result, TypeSymbol? type = null)
        {
            Debug.Assert(result.Type is { });
            var resultType = type ?? result.Type;
            return new BoundSequence(Syntax, ImmutableArray<LocalSymbol>.Empty, sideEffects.AsImmutableOrNull(), result, resultType) { WasCompilerGenerated = true };
        }
 
        public BoundExpression Sequence(ImmutableArray<LocalSymbol> locals, ImmutableArray<BoundExpression> sideEffects, BoundExpression result)
        {
            Debug.Assert(result.Type is { });
            return
                locals.IsDefaultOrEmpty && sideEffects.IsDefaultOrEmpty
                ? result
                : new BoundSequence(Syntax, locals, sideEffects, result, result.Type) { WasCompilerGenerated = true };
        }
 
        public BoundSpillSequence SpillSequence(ImmutableArray<LocalSymbol> locals, ImmutableArray<BoundStatement> sideEffects, BoundExpression result)
        {
            Debug.Assert(result.Type is { });
            return new BoundSpillSequence(Syntax, locals, sideEffects, result, result.Type) { WasCompilerGenerated = true };
        }
 
        /// <summary>
        /// An internal helper class for building a switch statement.
        /// </summary>
        internal readonly struct SyntheticSwitchSection
        {
            public readonly ImmutableArray<int> Values;
            public readonly ImmutableArray<BoundStatement> Statements;
 
            public SyntheticSwitchSection(ImmutableArray<int> values, ImmutableArray<BoundStatement> statements)
            {
                Values = values;
                Statements = statements;
            }
        }
 
        public SyntheticSwitchSection SwitchSection(int value, params BoundStatement[] statements)
            => SwitchSection(ImmutableArray.Create(value), statements);
 
        public SyntheticSwitchSection SwitchSection(ImmutableArray<int> values, params BoundStatement[] statements)
            => new(values, ImmutableArray.Create(statements));
 
        /// <summary>
        /// Produce an int switch.
        /// </summary>
        public BoundStatement Switch(BoundExpression ex, ImmutableArray<SyntheticSwitchSection> sections)
        {
            Debug.Assert(ex.Type is { SpecialType: CodeAnalysis.SpecialType.System_Int32 });
 
            if (sections.Length == 0)
            {
                return ExpressionStatement(ex);
            }
 
            CheckSwitchSections(sections);
 
            GeneratedLabelSymbol breakLabel = new GeneratedLabelSymbol("break");
 
            var caseBuilder = ArrayBuilder<(ConstantValue Value, LabelSymbol label)>.GetInstance();
            var statements = ArrayBuilder<BoundStatement>.GetInstance();
            statements.Add(null!); // placeholder at statements[0] for the dispatch
            foreach (var section in sections)
            {
                LabelSymbol sectionLabel = new GeneratedLabelSymbol("case " + section.Values[0]);
                statements.Add(Label(sectionLabel));
                statements.AddRange(section.Statements);
 
                foreach (var value in section.Values)
                {
                    caseBuilder.Add((ConstantValue.Create(value), sectionLabel));
                }
            }
 
            statements.Add(Label(breakLabel));
            Debug.Assert(statements[0] is null);
            statements[0] = new BoundSwitchDispatch(Syntax, ex, caseBuilder.ToImmutableAndFree(), breakLabel, lengthBasedStringSwitchDataOpt: null) { WasCompilerGenerated = true };
            return Block(statements.ToImmutableAndFree());
        }
 
        /// <summary>
        /// Check for (and assert that there are no) duplicate case labels in the switch.
        /// </summary>
        /// <param name="sections"></param>
        [Conditional("DEBUG")]
        private static void CheckSwitchSections(ImmutableArray<SyntheticSwitchSection> sections)
        {
            var labels = new HashSet<int>();
            foreach (var s in sections)
            {
                foreach (var v2 in s.Values)
                {
                    Debug.Assert(!labels.Contains(v2));
                    labels.Add(v2);
                }
            }
        }
 
        public BoundGotoStatement Goto(LabelSymbol label)
        {
            return new BoundGotoStatement(Syntax, label) { WasCompilerGenerated = true };
        }
 
        public BoundLabelStatement Label(LabelSymbol label)
        {
            return new BoundLabelStatement(Syntax, label) { WasCompilerGenerated = true };
        }
 
        public BoundLiteral Literal(Boolean value)
        {
            return new BoundLiteral(Syntax, ConstantValue.Create(value), SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Boolean)) { WasCompilerGenerated = true };
        }
 
        public BoundLiteral Literal(string? value)
        {
            var stringConst = ConstantValue.Create(value);
            return StringLiteral(stringConst);
        }
 
        public BoundLiteral StringLiteral(ConstantValue stringConst)
        {
            Debug.Assert(stringConst.IsString || stringConst.IsNull);
            return new BoundLiteral(Syntax, stringConst, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_String)) { WasCompilerGenerated = true };
        }
 
        public BoundLiteral StringLiteral(String stringValue)
        {
            return StringLiteral(ConstantValue.Create(stringValue));
        }
 
        public BoundLiteral CharLiteral(ConstantValue charConst)
        {
            Debug.Assert(charConst.IsChar || charConst.IsDefaultValue);
            return new BoundLiteral(Syntax, charConst, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Char)) { WasCompilerGenerated = true };
        }
 
        public BoundLiteral CharLiteral(Char charValue)
        {
            return CharLiteral(ConstantValue.Create(charValue));
        }
 
        public BoundArrayLength ArrayLength(BoundExpression array)
        {
            Debug.Assert(array.Type is { TypeKind: TypeKind.Array });
            return new BoundArrayLength(Syntax, array, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Int32));
        }
 
        public BoundArrayAccess ArrayAccessFirstElement(BoundExpression array)
        {
            Debug.Assert(array.Type is { TypeKind: TypeKind.Array });
            int rank = ((ArrayTypeSymbol)array.Type).Rank;
            ImmutableArray<BoundExpression> firstElementIndices = ArrayBuilder<BoundExpression>.GetInstance(rank, Literal(0)).ToImmutableAndFree();
            return ArrayAccess(array, firstElementIndices);
        }
 
        public BoundArrayAccess ArrayAccess(BoundExpression array, params BoundExpression[] indices)
        {
            return ArrayAccess(array, indices.AsImmutableOrNull());
        }
 
        public BoundArrayAccess ArrayAccess(BoundExpression array, ImmutableArray<BoundExpression> indices)
        {
            Debug.Assert(array.Type is { TypeKind: TypeKind.Array });
            return new BoundArrayAccess(Syntax, array, indices, ((ArrayTypeSymbol)array.Type).ElementType);
        }
 
        public BoundStatement BaseInitialization()
        {
            // TODO: add diagnostics for when things fall apart
            Debug.Assert(CurrentFunction is { });
            NamedTypeSymbol baseType = CurrentFunction.ThisParameter.Type.BaseTypeNoUseSiteDiagnostics;
            var ctor = baseType.InstanceConstructors.Single(c => c.ParameterCount == 0);
            return new BoundExpressionStatement(Syntax, Call(Base(baseType), ctor)) { WasCompilerGenerated = true };
        }
 
        public BoundStatement SequencePoint(SyntaxNode syntax, BoundStatement statement)
        {
            return new BoundSequencePoint(syntax, statement);
        }
 
        public BoundStatement SequencePointWithSpan(CSharpSyntaxNode syntax, TextSpan span, BoundStatement statement)
        {
            return new BoundSequencePointWithSpan(syntax, statement, span);
        }
 
        public BoundStatement HiddenSequencePoint(BoundStatement? statementOpt = null)
        {
            return BoundSequencePoint.CreateHidden(statementOpt);
        }
 
        public BoundStatement ThrowNull()
        {
            return Throw(Null(Binder.GetWellKnownType(Compilation, Microsoft.CodeAnalysis.WellKnownType.System_Exception, Diagnostics, Syntax.Location)));
        }
 
        public BoundExpression ThrowExpression(BoundExpression thrown, TypeSymbol type)
        {
            return new BoundThrowExpression(thrown.Syntax, thrown, type) { WasCompilerGenerated = true };
        }
 
        public BoundExpression Null(TypeSymbol type)
        {
            return Null(type, Syntax);
        }
 
        // Produce a ByRef null of given type, like `ref T Unsafe.NullRef<T>()`.
        public BoundExpression NullRef(TypeWithAnnotations type)
        {
            // *default(T*)
            return new BoundPointerIndirectionOperator(Syntax, Default(new PointerTypeSymbol(type)), refersToLocation: false, type.Type);
        }
 
        public static BoundExpression Null(TypeSymbol type, SyntaxNode syntax)
        {
            Debug.Assert(type.CanBeAssignedNull());
            BoundExpression nullLiteral = new BoundLiteral(syntax, ConstantValue.Null, type) { WasCompilerGenerated = true };
            return type.IsPointerOrFunctionPointer()
                ? BoundConversion.SynthesizedNonUserDefined(syntax, nullLiteral, Conversion.NullToPointer, type)
                : nullLiteral;
        }
 
        public BoundTypeExpression Type(TypeSymbol type)
        {
            return new BoundTypeExpression(Syntax, null, type) { WasCompilerGenerated = true };
        }
 
        public BoundExpression Typeof(WellKnownType type, TypeSymbol systemType)
        {
            return Typeof(WellKnownType(type), systemType);
        }
 
        public BoundExpression Typeof(TypeSymbol type, TypeSymbol systemType)
        {
            Debug.Assert(systemType.ExtendedSpecialType == InternalSpecialType.System_Type ||
                         systemType.Equals(Compilation.GetWellKnownType(CodeAnalysis.WellKnownType.System_Type), TypeCompareKind.AllIgnoreOptions));
 
            MethodSymbol getTypeFromHandle;
 
            if (systemType.ExtendedSpecialType == InternalSpecialType.System_Type)
            {
                getTypeFromHandle = SpecialMethod(CodeAnalysis.SpecialMember.System_Type__GetTypeFromHandle);
            }
            else
            {
                getTypeFromHandle = WellKnownMethod(CodeAnalysis.WellKnownMember.System_Type__GetTypeFromHandle);
            }
 
            Debug.Assert(TypeSymbol.Equals(systemType, getTypeFromHandle.ReturnType, TypeCompareKind.AllIgnoreOptions));
 
            return new BoundTypeOfOperator(
                Syntax,
                Type(type),
                getTypeFromHandle,
                systemType)
            { WasCompilerGenerated = true };
        }
 
        public BoundExpression Typeof(TypeWithAnnotations type, TypeSymbol systemType)
        {
            return Typeof(type.Type, systemType);
        }
 
        public ImmutableArray<BoundExpression> TypeOfs(ImmutableArray<TypeWithAnnotations> typeArguments, TypeSymbol systemType)
        {
            return typeArguments.SelectAsArray(Typeof, systemType);
        }
 
        public BoundExpression TypeofDynamicOperationContextType()
        {
            Debug.Assert(this.CompilationState is { DynamicOperationContextType: { } });
            return Typeof(this.CompilationState.DynamicOperationContextType, WellKnownType(CodeAnalysis.WellKnownType.System_Type));
        }
 
        public BoundExpression Sizeof(TypeSymbol type)
        {
            return new BoundSizeOfOperator(Syntax, Type(type), Binder.GetConstantSizeOf(type), SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Int32)) { WasCompilerGenerated = true };
        }
 
        internal BoundExpression ConstructorInfo(MethodSymbol ctor)
        {
            NamedTypeSymbol constructorInfo = WellKnownType(Microsoft.CodeAnalysis.WellKnownType.System_Reflection_ConstructorInfo);
 
            var result = new BoundMethodInfo(
                Syntax,
                ctor,
                GetMethodFromHandleMethod(ctor.ContainingType, constructorInfo),
                constructorInfo)
            { WasCompilerGenerated = true };
 
#if DEBUG
            var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
            Debug.Assert(result.Type.IsErrorType() || result.Type!.IsDerivedFrom(result.GetMethodFromHandle!.ReturnType, TypeCompareKind.AllIgnoreOptions, ref discardedUseSiteInfo));
#endif
            return result;
        }
 
        public BoundExpression MethodDefIndex(MethodSymbol method)
        {
            return new BoundMethodDefIndex(
                Syntax,
                method,
                SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Int32))
            { WasCompilerGenerated = true };
        }
 
        public BoundExpression LocalId(LocalSymbol symbol)
        {
            return new BoundLocalId(
                Syntax,
                symbol,
                hoistedField: null,
                SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Int32))
            { WasCompilerGenerated = true };
        }
 
        public BoundExpression ParameterId(ParameterSymbol symbol)
        {
            return new BoundParameterId(
                Syntax,
                symbol,
                hoistedField: null,
                SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Int32))
            { WasCompilerGenerated = true };
        }
 
        public BoundExpression StateMachineInstanceId()
        {
            return new BoundStateMachineInstanceId(
                Syntax,
                SpecialType(Microsoft.CodeAnalysis.SpecialType.System_UInt64))
            { WasCompilerGenerated = true };
        }
 
        /// <summary>
        /// Synthesizes an expression that evaluates to the current module's MVID.
        /// </summary>
        /// <returns></returns>
        public BoundExpression ModuleVersionId()
        {
            return new BoundModuleVersionId(Syntax, WellKnownType(Microsoft.CodeAnalysis.WellKnownType.System_Guid)) { WasCompilerGenerated = true };
        }
 
        public BoundExpression ModuleVersionIdString()
        {
            return new BoundModuleVersionIdString(Syntax, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_String)) { WasCompilerGenerated = true };
        }
 
        public BoundExpression InstrumentationPayloadRoot(int analysisKind, TypeSymbol payloadType)
        {
            return new BoundInstrumentationPayloadRoot(Syntax, analysisKind, payloadType) { WasCompilerGenerated = true };
        }
 
        public BoundExpression ThrowIfModuleCancellationRequested()
            => new BoundThrowIfModuleCancellationRequested(Syntax, SpecialType(CodeAnalysis.SpecialType.System_Void)) { WasCompilerGenerated = true };
 
        public BoundExpression ModuleCancellationToken()
            => new ModuleCancellationTokenExpression(Syntax, WellKnownType(CodeAnalysis.WellKnownType.System_Threading_CancellationToken)) { WasCompilerGenerated = true };
 
        public BoundExpression MaximumMethodDefIndex()
        {
            return new BoundMaximumMethodDefIndex(
                Syntax,
                SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Int32))
            { WasCompilerGenerated = true };
        }
 
        /// <summary>
        /// Synthesizes an expression that evaluates to the index of a source document in the table of debug source documents.
        /// </summary>
        public BoundExpression SourceDocumentIndex(Cci.DebugSourceDocument document)
        {
            return new BoundSourceDocumentIndex(
                Syntax,
                document,
                SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Int32))
            { WasCompilerGenerated = true };
        }
 
        public BoundExpression MethodInfo(MethodSymbol method, TypeSymbol systemReflectionMethodInfo)
        {
            // The least overridden virtual method is only called for value type receivers
            // in special circumstances. These circumstances are exactly the checks performed by
            // MayUseCallForStructMethod (which is also used by the emitter when determining
            // whether or not to call a method with a value type receiver directly).
            if (!method.ContainingType.IsValueType || !Microsoft.CodeAnalysis.CSharp.CodeGen.CodeGenerator.MayUseCallForStructMethod(method))
            {
                method = method.GetConstructedLeastOverriddenMethod(this.CompilationState.Type, requireSameReturnType: true);
            }
 
            var result = new BoundMethodInfo(
                Syntax,
                method,
                GetMethodFromHandleMethod(method.ContainingType, systemReflectionMethodInfo),
                systemReflectionMethodInfo)
            { WasCompilerGenerated = true };
 
#if DEBUG
            var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
            Debug.Assert(result.Type.IsErrorType() || result.Type!.IsDerivedFrom(result.GetMethodFromHandle!.ReturnType, TypeCompareKind.AllIgnoreOptions, ref discardedUseSiteInfo));
#endif
            return result;
        }
 
        public BoundExpression FieldInfo(FieldSymbol field)
        {
            return new BoundFieldInfo(
                Syntax,
                field,
                GetFieldFromHandleMethod(field.ContainingType),
                WellKnownType(Microsoft.CodeAnalysis.WellKnownType.System_Reflection_FieldInfo))
            { WasCompilerGenerated = true };
        }
 
        private MethodSymbol GetMethodFromHandleMethod(NamedTypeSymbol methodContainer, TypeSymbol systemReflectionMethodOrConstructorInfo)
        {
            Debug.Assert(systemReflectionMethodOrConstructorInfo.ExtendedSpecialType == InternalSpecialType.System_Reflection_MethodInfo ||
                         systemReflectionMethodOrConstructorInfo.Equals(Compilation.GetWellKnownType(CodeAnalysis.WellKnownType.System_Reflection_MethodInfo), TypeCompareKind.AllIgnoreOptions) ||
                         systemReflectionMethodOrConstructorInfo.Equals(Compilation.GetWellKnownType(CodeAnalysis.WellKnownType.System_Reflection_ConstructorInfo), TypeCompareKind.AllIgnoreOptions));
 
            bool isNotInGenericType = (methodContainer.AllTypeArgumentCount() == 0 && !methodContainer.IsAnonymousType);
 
            if (systemReflectionMethodOrConstructorInfo.ExtendedSpecialType == InternalSpecialType.System_Reflection_MethodInfo)
            {
                return SpecialMethod(
                    isNotInGenericType ?
                        CodeAnalysis.SpecialMember.System_Reflection_MethodBase__GetMethodFromHandle :
                        CodeAnalysis.SpecialMember.System_Reflection_MethodBase__GetMethodFromHandle2);
            }
            else
            {
                return WellKnownMethod(
                    isNotInGenericType ?
                        CodeAnalysis.WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle :
                        CodeAnalysis.WellKnownMember.System_Reflection_MethodBase__GetMethodFromHandle2);
            }
        }
 
        private MethodSymbol GetFieldFromHandleMethod(NamedTypeSymbol fieldContainer)
        {
            return WellKnownMethod(
                (fieldContainer.AllTypeArgumentCount() == 0) ?
                CodeAnalysis.WellKnownMember.System_Reflection_FieldInfo__GetFieldFromHandle :
                CodeAnalysis.WellKnownMember.System_Reflection_FieldInfo__GetFieldFromHandle2);
        }
 
        public BoundExpression Convert(TypeSymbol type, BoundExpression arg, bool allowBoxingByRefLikeTypeParametersToObject = false)
        {
            Debug.Assert(!allowBoxingByRefLikeTypeParametersToObject || type.IsObjectType());
 
            if (TypeSymbol.Equals(type, arg.Type, TypeCompareKind.ConsiderEverything2))
            {
                return arg;
            }
 
            var useSiteInfo =
#if DEBUG
                    CompoundUseSiteInfo<AssemblySymbol>.DiscardedDependencies;
#else
                    CompoundUseSiteInfo<AssemblySymbol>.Discarded;
#endif 
            Conversion c = Compilation.Conversions.ClassifyConversionFromExpression(arg, type, isChecked: false, ref useSiteInfo);
 
            if (allowBoxingByRefLikeTypeParametersToObject && !c.Exists &&
                arg.Type is TypeParameterSymbol { AllowsRefLikeType: true } && type.IsObjectType())
            {
                c = Conversion.Boxing;
            }
 
            Debug.Assert(c.Exists);
            // The use-site diagnostics should be reported earlier, and we shouldn't get to lowering if they're errors.
            Debug.Assert(!useSiteInfo.HasErrors);
 
            return Convert(type, arg, c);
        }
 
        public BoundExpression Convert(TypeSymbol type, BoundExpression arg, Conversion conversion, bool isChecked = false)
        {
            // NOTE: We can see user-defined conversions at this point because there are places in the bound tree where
            // the binder stashes Conversion objects for later consumption (e.g. foreach, nullable, increment).
            if (conversion.Method is { } && !TypeSymbol.Equals(conversion.Method.Parameters[0].Type, arg.Type, TypeCompareKind.ConsiderEverything2))
            {
                arg = Convert(conversion.Method.Parameters[0].Type, arg);
            }
 
            if (conversion.Kind == ConversionKind.ImplicitReference && arg.IsLiteralNull())
            {
                return Null(type);
            }
 
            Debug.Assert(arg.Type is { });
            if (conversion.Kind == ConversionKind.ExplicitNullable &&
                arg.Type.IsNullableType() &&
                arg.Type.GetNullableUnderlyingType().Equals(type, TypeCompareKind.AllIgnoreOptions))
            {
                // A conversion to unbox a nullable value is produced when binding a pattern-matching
                // operation from an operand of type T? to a pattern of type T.
                return this.Call(arg, this.SpecialMethod(CodeAnalysis.SpecialMember.System_Nullable_T_get_Value).AsMember((NamedTypeSymbol)arg.Type));
            }
 
            return new BoundConversion(Syntax, arg, conversion, @checked: isChecked, explicitCastInCode: true, conversionGroupOpt: null, null, type) { WasCompilerGenerated = true };
        }
 
        public BoundExpression ArrayOrEmpty(TypeSymbol elementType, BoundExpression[] elements)
        {
            return ArrayOrEmpty(elementType, elements.AsImmutable());
        }
 
        /// <summary>
        /// Helper that will use Array.Empty if available and elements have 0 length
        /// NOTE: it is valid only if we know that the API that is being called will not
        ///       retain or use the array argument for any purpose (like locking or key in a hash table)
        ///       Typical example of valid use is Linq.Expressions factories - they do not make any
        ///       assumptions about array arguments and do not keep them or rely on their identity.
        /// </summary>
        public BoundExpression ArrayOrEmpty(TypeSymbol elementType, ImmutableArray<BoundExpression> elements)
        {
            if (elements.Length == 0)
            {
                MethodSymbol? arrayEmpty = SpecialMethod(CodeAnalysis.SpecialMember.System_Array__Empty, isOptional: true);
                if (arrayEmpty is { })
                {
                    arrayEmpty = arrayEmpty.Construct(ImmutableArray.Create(elementType));
                    return Call(null, arrayEmpty);
                }
            }
 
            return Array(elementType, elements);
        }
 
        public BoundExpression Array(TypeSymbol elementType, ImmutableArray<BoundExpression> elements)
        {
            return new BoundArrayCreation(
                Syntax,
                ImmutableArray.Create<BoundExpression>(Literal(elements.Length)),
                new BoundArrayInitialization(Syntax, isInferred: false, elements) { WasCompilerGenerated = true },
                Compilation.CreateArrayTypeSymbol(elementType));
        }
 
        public BoundExpression Array(TypeSymbol elementType, BoundExpression length)
        {
            return new BoundArrayCreation(
               Syntax,
               ImmutableArray.Create<BoundExpression>(length),
               null,
               Compilation.CreateArrayTypeSymbol(elementType))
            { WasCompilerGenerated = true };
        }
 
        internal BoundExpression Default(TypeSymbol type)
        {
            return Default(type, Syntax);
        }
 
        internal static BoundExpression Default(TypeSymbol type, SyntaxNode syntax)
        {
            return new BoundDefaultExpression(syntax, type) { WasCompilerGenerated = true };
        }
 
        internal BoundStatement Try(
            BoundBlock tryBlock,
            ImmutableArray<BoundCatchBlock> catchBlocks,
            BoundBlock? finallyBlock = null,
            LabelSymbol? finallyLabel = null)
        {
            return new BoundTryStatement(Syntax, tryBlock, catchBlocks, finallyBlock, finallyLabel) { WasCompilerGenerated = true };
        }
 
        internal ImmutableArray<BoundCatchBlock> CatchBlocks(
            params BoundCatchBlock[] catchBlocks)
        {
            return catchBlocks.AsImmutableOrNull();
        }
 
        internal BoundCatchBlock Catch(
            LocalSymbol local,
            BoundBlock block)
        {
            var source = Local(local);
            return new BoundCatchBlock(Syntax, ImmutableArray.Create(local), source, source.Type, exceptionFilterPrologueOpt: null, exceptionFilterOpt: null, body: block, isSynthesizedAsyncCatchAll: false);
        }
 
        internal BoundCatchBlock Catch(
            BoundExpression source,
            BoundBlock block)
        {
            return new BoundCatchBlock(Syntax, ImmutableArray<LocalSymbol>.Empty, source, source.Type, exceptionFilterPrologueOpt: null, exceptionFilterOpt: null, body: block, isSynthesizedAsyncCatchAll: false);
        }
 
        internal BoundTryStatement Fault(BoundBlock tryBlock, BoundBlock faultBlock)
        {
            return new BoundTryStatement(Syntax, tryBlock, ImmutableArray<BoundCatchBlock>.Empty, faultBlock, finallyLabelOpt: null, preferFaultHandler: true);
        }
 
        internal BoundExpression NullOrDefault(TypeSymbol typeSymbol)
        {
            return NullOrDefault(typeSymbol, this.Syntax);
        }
 
        internal static BoundExpression NullOrDefault(TypeSymbol typeSymbol, SyntaxNode syntax)
        {
            return typeSymbol.IsReferenceType ? Null(typeSymbol, syntax) : Default(typeSymbol, syntax);
        }
 
        internal BoundExpression Not(BoundExpression expression)
        {
            Debug.Assert(expression is { Type: { SpecialType: CodeAnalysis.SpecialType.System_Boolean } });
            return new BoundUnaryOperator(expression.Syntax, UnaryOperatorKind.BoolLogicalNegation, expression, null, null, constrainedToTypeOpt: null, LookupResultKind.Viable, expression.Type);
        }
 
        /// <summary>
        /// Takes an expression and returns the bound local expression "temp"
        /// and the bound assignment expression "temp = expr".
        /// </summary>
        public BoundLocal StoreToTemp(
            BoundExpression argument,
            out BoundAssignmentOperator store,
            RefKind refKind = RefKind.None,
            SynthesizedLocalKind kind = SynthesizedLocalKind.LoweringTemp,
            bool isKnownToReferToTempIfReferenceType = false,
            SyntaxNode? syntaxOpt = null
#if DEBUG
            , [CallerLineNumber] int callerLineNumber = 0
            , [CallerFilePath] string? callerFilePath = null
#endif
            )
        {
            Debug.Assert(argument.Type is { });
            MethodSymbol? containingMethod = this.CurrentFunction;
            Debug.Assert(containingMethod is { });
            Debug.Assert(kind != SynthesizedLocalKind.UserDefined);
 
            switch (refKind)
            {
                case RefKind.Out:
                    refKind = RefKind.Ref;
                    break;
 
                case RefKind.In:
                    if (!Binder.HasHome(argument,
                                        Binder.AddressKind.ReadOnly,
                                        containingMethod,
                                        Compilation.IsPeVerifyCompatEnabled,
                                        stackLocalsOpt: null))
                    {
                        // If there was an explicit 'in' on the argument then we should have verified
                        // earlier that we always have a home.
                        Debug.Assert(argument.GetRefKind() != RefKind.In);
                        refKind = RefKind.None;
                    }
                    break;
                case RefKindExtensions.StrictIn:
                case RefKind.None:
                case RefKind.Ref:
                    break;
                default:
                    throw ExceptionUtilities.UnexpectedValue(refKind);
            }
 
            var syntax = argument.Syntax;
            var type = argument.Type;
 
            var local = new BoundLocal(
                syntax,
                new SynthesizedLocal(
                    containingMethod,
                    TypeWithAnnotations.Create(type),
                    kind,
#if DEBUG
                    createdAtLineNumber: callerLineNumber,
                    createdAtFilePath: callerFilePath,
#endif
                    syntaxOpt: syntaxOpt ?? (kind.IsLongLived() ? syntax : null),
                    isPinned: false,
                    isKnownToReferToTempIfReferenceType: isKnownToReferToTempIfReferenceType,
                    refKind: refKind),
                null,
            type);
 
            store = new BoundAssignmentOperator(
                syntax,
                local,
                argument,
                type,
                isRef: refKind != RefKind.None);
 
            return local;
        }
 
        internal BoundStatement NoOp(NoOpStatementFlavor noOpStatementFlavor)
        {
            return new BoundNoOpStatement(Syntax, noOpStatementFlavor);
        }
 
        internal BoundLocal MakeTempForDiscard(BoundDiscardExpression node, ArrayBuilder<LocalSymbol> temps)
        {
            LocalSymbol temp;
            BoundLocal result = MakeTempForDiscard(node, out temp);
            temps.Add(temp);
            return result;
        }
 
        internal BoundLocal MakeTempForDiscard(BoundDiscardExpression node, out LocalSymbol temp)
        {
            Debug.Assert(node.Type is { });
            temp = new SynthesizedLocal(this.CurrentFunction, TypeWithAnnotations.Create(node.Type), SynthesizedLocalKind.LoweringTemp);
 
            return new BoundLocal(node.Syntax, temp, constantValueOpt: null, type: node.Type) { WasCompilerGenerated = true };
        }
 
        internal ImmutableArray<BoundExpression> MakeTempsForDiscardArguments(ImmutableArray<BoundExpression> arguments, ArrayBuilder<LocalSymbol> builder)
        {
            var discardsPresent = arguments.Any(static a => a.Kind == BoundKind.DiscardExpression);
 
            if (discardsPresent)
            {
                arguments = arguments.SelectAsArray(
                    (arg, t) => arg.Kind == BoundKind.DiscardExpression ? t.factory.MakeTempForDiscard((BoundDiscardExpression)arg, t.builder) : arg,
                    (factory: this, builder: builder));
            }
 
            return arguments;
        }
 
#nullable disable
        internal BoundExpression MakeNullCheck(SyntaxNode syntax, BoundExpression rewrittenExpr, BinaryOperatorKind operatorKind)
        {
            Debug.Assert((operatorKind == BinaryOperatorKind.Equal) || (operatorKind == BinaryOperatorKind.NotEqual) ||
                (operatorKind == BinaryOperatorKind.NullableNullEqual) || (operatorKind == BinaryOperatorKind.NullableNullNotEqual));
 
            TypeSymbol exprType = rewrittenExpr.Type;
 
            // Don't even call this method if the expression cannot be nullable.
            Debug.Assert(
                (object)exprType == null ||
                exprType.IsNullableTypeOrTypeParameter() ||
                !exprType.IsValueType ||
                exprType.IsPointerOrFunctionPointer());
 
            TypeSymbol boolType = Compilation.GetSpecialType(CodeAnalysis.SpecialType.System_Boolean);
 
            // Fold compile-time comparisons.
            if (rewrittenExpr.ConstantValueOpt != null)
            {
                switch (operatorKind)
                {
                    case BinaryOperatorKind.Equal:
                        return Literal(ConstantValue.Create(rewrittenExpr.ConstantValueOpt.IsNull, ConstantValueTypeDiscriminator.Boolean), boolType);
                    case BinaryOperatorKind.NotEqual:
                        return Literal(ConstantValue.Create(rewrittenExpr.ConstantValueOpt.IsNull, ConstantValueTypeDiscriminator.Boolean), boolType);
                }
            }
 
            TypeSymbol objectType = SpecialType(CodeAnalysis.SpecialType.System_Object);
 
            if ((object)exprType != null)
            {
                if (exprType.Kind == SymbolKind.TypeParameter)
                {
                    // Box type parameters.
                    rewrittenExpr = Convert(objectType, rewrittenExpr, Conversion.Boxing);
                }
                else if (exprType.IsNullableType())
                {
                    operatorKind |= BinaryOperatorKind.NullableNull;
                }
            }
            if (operatorKind == BinaryOperatorKind.NullableNullEqual || operatorKind == BinaryOperatorKind.NullableNullNotEqual)
            {
                return RewriteNullableNullEquality(syntax, operatorKind, rewrittenExpr, Literal(ConstantValue.Null, objectType), boolType);
            }
            else
            {
                return Binary(operatorKind, boolType, rewrittenExpr, Null(objectType));
            }
        }
 
        internal BoundExpression MakeNullableHasValue(SyntaxNode syntax, BoundExpression expression)
        {
            // https://github.com/dotnet/roslyn/issues/58335: consider restoring the 'private' accessibility of 'static LocalRewriter.UnsafeGetNullableMethod()'
            return BoundCall.Synthesized(
                syntax,
                expression,
                initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown,
                LocalRewriter.UnsafeGetNullableMethod(syntax, expression.Type, CodeAnalysis.SpecialMember.System_Nullable_T_get_HasValue, Compilation, Diagnostics));
        }
 
        internal BoundExpression RewriteNullableNullEquality(
            SyntaxNode syntax,
            BinaryOperatorKind kind,
            BoundExpression loweredLeft,
            BoundExpression loweredRight,
            TypeSymbol returnType)
        {
            // This handles the case where we have a nullable user-defined struct type compared against null, eg:
            //
            // struct S {} ... S? s = whatever; if (s != null)
            //
            // If S does not define an overloaded != operator then this is lowered to s.HasValue.
            //
            // If the type already has a user-defined or built-in operator then comparing to null is
            // treated as a lifted equality operator.
 
            Debug.Assert(loweredLeft != null);
            Debug.Assert(loweredRight != null);
            Debug.Assert((object)returnType != null);
            Debug.Assert(returnType.SpecialType == CodeAnalysis.SpecialType.System_Boolean);
            Debug.Assert(loweredLeft.IsLiteralNull() != loweredRight.IsLiteralNull());
 
            BoundExpression nullable = loweredRight.IsLiteralNull() ? loweredLeft : loweredRight;
 
            // If the other side is known to always be null then we can simply generate true or false, as appropriate.
 
            if (LocalRewriter.NullableNeverHasValue(nullable))
            {
                return Literal(kind == BinaryOperatorKind.NullableNullEqual);
            }
 
            BoundExpression nonNullValue = LocalRewriter.NullableAlwaysHasValue(nullable);
            if (nonNullValue != null)
            {
                // We have something like "if (new int?(M()) != null)". We can optimize this to
                // evaluate M() for its side effects and then result in true or false, as appropriate.
 
                // TODO: If the expression has no side effects then it can be optimized away here as well.
 
                return new BoundSequence(
                    syntax: syntax,
                    locals: ImmutableArray<LocalSymbol>.Empty,
                    sideEffects: ImmutableArray.Create<BoundExpression>(nonNullValue),
                    value: Literal(kind == BinaryOperatorKind.NullableNullNotEqual),
                    type: returnType);
            }
 
            // arr?.Length == null
            var conditionalAccess = nullable as BoundLoweredConditionalAccess;
            if (conditionalAccess != null &&
                (conditionalAccess.WhenNullOpt == null || conditionalAccess.WhenNullOpt.IsDefaultValue()))
            {
                BoundExpression whenNotNull = RewriteNullableNullEquality(
                    syntax,
                    kind,
                    conditionalAccess.WhenNotNull,
                    loweredLeft.IsLiteralNull() ? loweredLeft : loweredRight,
                    returnType);
 
                var whenNull = kind == BinaryOperatorKind.NullableNullEqual ? Literal(true) : null;
 
                return conditionalAccess.Update(conditionalAccess.Receiver, conditionalAccess.HasValueMethodOpt, whenNotNull, whenNull, conditionalAccess.Id, conditionalAccess.ForceCopyOfNullableValueType, whenNotNull.Type);
            }
 
            BoundExpression call = MakeNullableHasValue(syntax, nullable);
            BoundExpression result = kind == BinaryOperatorKind.NullableNullNotEqual ?
                call :
                new BoundUnaryOperator(syntax, UnaryOperatorKind.BoolLogicalNegation, call, ConstantValue.NotAvailable, methodOpt: null, constrainedToTypeOpt: null, LookupResultKind.Viable, returnType);
 
            return result;
        }
        // https://github.com/dotnet/roslyn/issues/58335: Re-enable annotations
#nullable enable
    }
}