File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\Utilities\SpeculationAnalyzer.cs
Web Access
Project: src\src\Workspaces\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Workspaces.csproj (Microsoft.CodeAnalysis.CSharp.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Xml.Serialization;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.LanguageService;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Utilities;
 
/// <summary>
/// Helper class to analyze the semantic effects of a speculated syntax node replacement on the parenting nodes.
/// Given an expression node from a syntax tree and a new expression from a different syntax tree,
/// it replaces the expression with the new expression to create a speculated syntax tree.
/// It uses the original tree's semantic model to create a speculative semantic model and verifies that
/// the syntax replacement doesn't break the semantics of any parenting nodes of the original expression.
/// </summary>
internal class SpeculationAnalyzer : AbstractSpeculationAnalyzer<
    ExpressionSyntax,
    TypeSyntax,
    AttributeSyntax,
    ArgumentSyntax,
    CommonForEachStatementSyntax,
    ThrowStatementSyntax,
    InvocationExpressionSyntax,
    Conversion>
{
    /// <summary>
    /// Creates a semantic analyzer for speculative syntax replacement.
    /// </summary>
    /// <param name="expression">Original expression to be replaced.</param>
    /// <param name="newExpression">New expression to replace the original expression.</param>
    /// <param name="semanticModel">Semantic model of <paramref name="expression"/> node's syntax tree.</param>
    /// <param name="cancellationToken">Cancellation token.</param>
    /// <param name="skipVerificationForReplacedNode">
    /// True if semantic analysis should be skipped for the replaced node and performed starting from parent of the original and replaced nodes.
    /// This could be the case when custom verifications are required to be done by the caller or
    /// semantics of the replaced expression are different from the original expression.
    /// </param>
    /// <param name="failOnOverloadResolutionFailuresInOriginalCode">
    /// True if semantic analysis should fail when any of the invocation expression ancestors of <paramref name="expression"/> in original code has overload resolution failures.
    /// </param>
    public SpeculationAnalyzer(
        ExpressionSyntax expression,
        ExpressionSyntax newExpression,
        SemanticModel semanticModel,
        CancellationToken cancellationToken,
        bool skipVerificationForReplacedNode = false,
        bool failOnOverloadResolutionFailuresInOriginalCode = false)
        : base(expression, newExpression, semanticModel, cancellationToken, skipVerificationForReplacedNode, failOnOverloadResolutionFailuresInOriginalCode)
    {
    }
 
    protected override CodeAnalysis.LanguageService.ISyntaxFacts SyntaxFactsService { get; } = CSharpSyntaxFacts.Instance;
 
    protected override bool CanAccessInstanceMemberThrough(ExpressionSyntax expression)
        => expression.Kind() is SyntaxKind.ThisExpression or SyntaxKind.BaseExpression;
 
    protected override SyntaxNode GetSemanticRootForSpeculation(ExpressionSyntax expression)
    {
        Debug.Assert(expression != null);
 
        SyntaxNode previousNode = null;
        SyntaxNode parentNodeToSpeculate = null;
        foreach (var node in expression.AncestorsAndSelf(ascendOutOfTrivia: false))
        {
            if (CanSpeculateOnNode(node))
            {
                // Only speculate on PrimaryConstructorBaseTypeSyntax if we are inside the argument list
                if (node.Kind() is not SyntaxKind.PrimaryConstructorBaseType ||
                    previousNode.Kind() is SyntaxKind.ArgumentList)
                {
                    parentNodeToSpeculate = node;
                }
            }
 
            previousNode = node;
        }
 
        return parentNodeToSpeculate ?? expression;
    }
 
    public static bool CanSpeculateOnNode(SyntaxNode node)
        => node is StatementSyntax(kind: not SyntaxKind.Block) or TypeSyntax or CrefSyntax ||
           node.Kind() is SyntaxKind.Attribute or
                          SyntaxKind.ThisConstructorInitializer or
                          SyntaxKind.BaseConstructorInitializer or
                          SyntaxKind.EqualsValueClause or
                          SyntaxKind.ArrowExpressionClause or
                          SyntaxKind.PrimaryConstructorBaseType;
 
    protected override void ValidateSpeculativeSemanticModel(SemanticModel speculativeSemanticModel, SyntaxNode nodeToSpeculate)
    {
        Debug.Assert(speculativeSemanticModel != null ||
            nodeToSpeculate is ExpressionSyntax ||
            this.SemanticRootOfOriginalExpression.GetAncestors().Any(
                node => node.Kind() is SyntaxKind.UnknownAccessorDeclaration or SyntaxKind.IncompleteMember or SyntaxKind.BracketedArgumentList),
            "SemanticModel.TryGetSpeculativeSemanticModel() API returned false.");
    }
 
    protected override SemanticModel CreateSpeculativeSemanticModel(SyntaxNode originalNode, SyntaxNode nodeToSpeculate, SemanticModel semanticModel)
        => CreateSpeculativeSemanticModelForNode(originalNode, nodeToSpeculate, semanticModel);
 
    public static SemanticModel CreateSpeculativeSemanticModelForNode(SyntaxNode originalNode, SyntaxNode nodeToSpeculate, SemanticModel semanticModel)
    {
        var position = originalNode.SpanStart;
        var isInNamespaceOrTypeContext = SyntaxFacts.IsInNamespaceOrTypeContext(originalNode as ExpressionSyntax);
        return CreateSpeculativeSemanticModelForNode(nodeToSpeculate, semanticModel, position, isInNamespaceOrTypeContext);
    }
 
    public static SemanticModel CreateSpeculativeSemanticModelForNode(SyntaxNode nodeToSpeculate, SemanticModel semanticModel, int position, bool isInNamespaceOrTypeContext)
    {
        if (semanticModel.IsSpeculativeSemanticModel)
        {
            // Chaining speculative model not supported, speculate off the original model.
            Debug.Assert(semanticModel.ParentModel != null);
            Debug.Assert(!semanticModel.ParentModel.IsSpeculativeSemanticModel);
            position = semanticModel.OriginalPositionForSpeculation;
            semanticModel = semanticModel.ParentModel;
        }
 
        SemanticModel speculativeModel;
        if (nodeToSpeculate is StatementSyntax statementNode)
        {
            semanticModel.TryGetSpeculativeSemanticModel(position, statementNode, out speculativeModel);
            return speculativeModel;
        }
 
        if (nodeToSpeculate is TypeSyntax typeNode)
        {
            var bindingOption = isInNamespaceOrTypeContext
                ? SpeculativeBindingOption.BindAsTypeOrNamespace
                : SpeculativeBindingOption.BindAsExpression;
            semanticModel.TryGetSpeculativeSemanticModel(position, typeNode, out speculativeModel, bindingOption);
            return speculativeModel;
        }
 
        if (nodeToSpeculate is CrefSyntax cref)
        {
            semanticModel.TryGetSpeculativeSemanticModel(position, cref, out speculativeModel);
            return speculativeModel;
        }
 
        switch (nodeToSpeculate.Kind())
        {
            case SyntaxKind.Attribute:
                semanticModel.TryGetSpeculativeSemanticModel(position, (AttributeSyntax)nodeToSpeculate, out speculativeModel);
                return speculativeModel;
 
            case SyntaxKind.BaseConstructorInitializer:
            case SyntaxKind.ThisConstructorInitializer:
                semanticModel.TryGetSpeculativeSemanticModel(position, (ConstructorInitializerSyntax)nodeToSpeculate, out speculativeModel);
                return speculativeModel;
 
            case SyntaxKind.EqualsValueClause:
                semanticModel.TryGetSpeculativeSemanticModel(position, (EqualsValueClauseSyntax)nodeToSpeculate, out speculativeModel);
                return speculativeModel;
 
            case SyntaxKind.ArrowExpressionClause:
                semanticModel.TryGetSpeculativeSemanticModel(position, (ArrowExpressionClauseSyntax)nodeToSpeculate, out speculativeModel);
                return speculativeModel;
 
            case SyntaxKind.PrimaryConstructorBaseType:
                semanticModel.TryGetSpeculativeSemanticModel(position, (PrimaryConstructorBaseTypeSyntax)nodeToSpeculate, out speculativeModel);
                return speculativeModel;
        }
 
        // CONSIDER: Do we care about this case?
        Debug.Assert(nodeToSpeculate is ExpressionSyntax);
        return null;
    }
 
    /// <summary>
    /// Determines whether performing the syntax replacement in one of the sibling nodes of the given lambda expressions will change the lambda binding semantics.
    /// This is done by first determining the lambda parameters whose type differs in the replaced lambda node.
    /// For each of these parameters, we find the descendant identifier name nodes in the lambda body and check if semantics of any of the parenting nodes of these
    /// identifier nodes have changed in the replaced lambda.
    /// </summary>
    public bool ReplacementChangesSemanticsOfUnchangedLambda(ExpressionSyntax originalLambda, ExpressionSyntax replacedLambda)
    {
        originalLambda = originalLambda.WalkDownParentheses();
        replacedLambda = replacedLambda.WalkDownParentheses();
 
        SyntaxNode originalLambdaBody, replacedLambdaBody;
        List<string> paramNames;
 
        switch (originalLambda.Kind())
        {
            case SyntaxKind.ParenthesizedLambdaExpression:
                {
                    var originalParenthesizedLambda = (ParenthesizedLambdaExpressionSyntax)originalLambda;
                    var originalParams = originalParenthesizedLambda.ParameterList.Parameters;
                    if (!originalParams.Any())
                    {
                        return false;
                    }
 
                    var replacedParenthesizedLambda = (ParenthesizedLambdaExpressionSyntax)replacedLambda;
                    var replacedParams = replacedParenthesizedLambda.ParameterList.Parameters;
                    Debug.Assert(originalParams.Count == replacedParams.Count);
 
                    paramNames = [];
                    for (var i = 0; i < originalParams.Count; i++)
                    {
                        var originalParam = originalParams[i];
                        var replacedParam = replacedParams[i];
                        if (!HaveSameParameterType(originalParam, replacedParam))
                        {
                            paramNames.Add(originalParam.Identifier.ValueText);
                        }
                    }
 
                    if (!paramNames.Any())
                    {
                        return false;
                    }
 
                    originalLambdaBody = originalParenthesizedLambda.Body;
                    replacedLambdaBody = replacedParenthesizedLambda.Body;
                    break;
                }
 
            case SyntaxKind.SimpleLambdaExpression:
                {
                    var originalSimpleLambda = (SimpleLambdaExpressionSyntax)originalLambda;
                    var replacedSimpleLambda = (SimpleLambdaExpressionSyntax)replacedLambda;
 
                    if (HaveSameParameterType(originalSimpleLambda.Parameter, replacedSimpleLambda.Parameter))
                    {
                        return false;
                    }
 
                    paramNames = [originalSimpleLambda.Parameter.Identifier.ValueText];
                    originalLambdaBody = originalSimpleLambda.Body;
                    replacedLambdaBody = replacedSimpleLambda.Body;
                    break;
                }
 
            default:
                throw ExceptionUtilities.UnexpectedValue(originalLambda.Kind());
        }
 
        var originalIdentifierNodes = originalLambdaBody.DescendantNodes().OfType<IdentifierNameSyntax>().Where(node => paramNames.Contains(node.Identifier.ValueText));
        if (!originalIdentifierNodes.Any())
        {
            return false;
        }
 
        var replacedIdentifierNodes = replacedLambdaBody.DescendantNodes().OfType<IdentifierNameSyntax>().Where(node => paramNames.Contains(node.Identifier.ValueText));
        return ReplacementChangesSemanticsForNodes(originalIdentifierNodes, replacedIdentifierNodes, originalLambdaBody);
    }
 
    private bool HaveSameParameterType(ParameterSyntax originalParam, ParameterSyntax replacedParam)
    {
        var originalParamType = this.OriginalSemanticModel.GetDeclaredSymbol(originalParam).Type;
        var replacedParamType = this.SpeculativeSemanticModel.GetDeclaredSymbol(replacedParam).Type;
        return Equals(originalParamType, replacedParamType);
    }
 
    private bool ReplacementChangesSemanticsForNodes(
        IEnumerable<IdentifierNameSyntax> originalIdentifierNodes,
        IEnumerable<IdentifierNameSyntax> replacedIdentifierNodes,
        SyntaxNode originalRoot)
    {
        Debug.Assert(originalIdentifierNodes.Any());
        Debug.Assert(originalIdentifierNodes.Count() == replacedIdentifierNodes.Count());
 
        var originalChildNodeEnum = originalIdentifierNodes.GetEnumerator();
        var replacedChildNodeEnum = replacedIdentifierNodes.GetEnumerator();
 
        while (originalChildNodeEnum.MoveNext())
        {
            replacedChildNodeEnum.MoveNext();
            if (ReplacementChangesSemantics(originalChildNodeEnum.Current, replacedChildNodeEnum.Current, originalRoot, skipVerificationForCurrentNode: true))
            {
                return true;
            }
        }
 
        return false;
    }
 
    protected override bool ReplacementChangesSemanticsForNodeLanguageSpecific(SyntaxNode currentOriginalNode, SyntaxNode currentReplacedNode, SyntaxNode previousOriginalNode, SyntaxNode previousReplacedNode)
    {
        Debug.Assert(previousOriginalNode == null || previousOriginalNode.Parent == currentOriginalNode);
        Debug.Assert(previousReplacedNode == null || previousReplacedNode.Parent == currentReplacedNode);
 
        if (currentOriginalNode.Kind() is SyntaxKind.CaseSwitchLabel or SyntaxKind.ConstantPattern)
        {
            var expression = (ExpressionSyntax)currentReplacedNode.ChildNodes().First();
            if (expression.WalkDownParentheses().IsKind(SyntaxKind.DefaultLiteralExpression))
            {
                // We can't have a default literal inside a case label or constant pattern.
                return true;
            }
        }
 
        if (currentOriginalNode is BinaryExpressionSyntax binaryExpression)
        {
            // If replacing the node will result in a broken binary expression, we won't remove it.
            return ReplacementBreaksBinaryExpression(binaryExpression, (BinaryExpressionSyntax)currentReplacedNode);
        }
        else if (currentOriginalNode.Kind() == SyntaxKind.LogicalNotExpression)
        {
            return !TypesAreCompatible(((PrefixUnaryExpressionSyntax)currentOriginalNode).Operand, ((PrefixUnaryExpressionSyntax)currentReplacedNode).Operand);
        }
        else if (currentOriginalNode.Kind() == SyntaxKind.ConditionalAccessExpression)
        {
            return ReplacementBreaksConditionalAccessExpression((ConditionalAccessExpressionSyntax)currentOriginalNode, (ConditionalAccessExpressionSyntax)currentReplacedNode);
        }
        else if (currentOriginalNode is AssignmentExpressionSyntax assignment)
        {
            // If replacing the node will result in a broken assignment expression, we won't remove it.
            return ReplacementBreaksAssignmentExpression(assignment, (AssignmentExpressionSyntax)currentReplacedNode);
        }
        else if (currentOriginalNode is SelectOrGroupClauseSyntax or OrderingSyntax)
        {
            return !SymbolsAreCompatible(currentOriginalNode, currentReplacedNode);
        }
        else if (currentOriginalNode is QueryClauseSyntax queryClause)
        {
            return ReplacementBreaksQueryClause(queryClause, (QueryClauseSyntax)currentReplacedNode);
        }
        else if (currentOriginalNode.Kind() == SyntaxKind.VariableDeclarator)
        {
            // Heuristic: If replacing the node will result in changing the type of a local variable
            // that is type-inferred, we won't remove it. It's possible to do this analysis, but it's
            // very expensive and the benefit to the user is small.
            var originalDeclarator = (VariableDeclaratorSyntax)currentOriginalNode;
            var newDeclarator = (VariableDeclaratorSyntax)currentReplacedNode;
 
            if (originalDeclarator.Initializer == null)
            {
                return newDeclarator.Initializer != null;
            }
            else if (newDeclarator.Initializer == null)
            {
                return true;
            }
 
            if (!originalDeclarator.Initializer.IsMissing &&
                originalDeclarator.IsTypeInferred(this.OriginalSemanticModel) &&
                !TypesAreCompatible(originalDeclarator.Initializer.Value, newDeclarator.Initializer.Value))
            {
                return true;
            }
 
            return false;
        }
        else if (currentOriginalNode is ConditionalExpressionSyntax originalExpression)
        {
            var newExpression = (ConditionalExpressionSyntax)currentReplacedNode;
 
            if (originalExpression.Condition != previousOriginalNode)
            {
                ExpressionSyntax originalOtherPartOfConditional, newOtherPartOfConditional;
 
                if (originalExpression.WhenTrue == previousOriginalNode)
                {
                    Debug.Assert(newExpression.WhenTrue == previousReplacedNode);
                    originalOtherPartOfConditional = originalExpression.WhenFalse;
                    newOtherPartOfConditional = newExpression.WhenFalse;
                }
                else
                {
                    Debug.Assert(newExpression.WhenFalse == previousReplacedNode);
                    originalOtherPartOfConditional = originalExpression.WhenTrue;
                    newOtherPartOfConditional = newExpression.WhenTrue;
                }
 
                var originalExpressionTypeInfo = this.OriginalSemanticModel.GetTypeInfo(originalExpression, this.CancellationToken);
                var newExpressionTypeInfo = this.SpeculativeSemanticModel.GetTypeInfo(newExpression, this.CancellationToken);
 
                var originalExpressionType = originalExpressionTypeInfo.Type;
                var newExpressionType = newExpressionTypeInfo.Type;
 
                // A conditional expression may have no type of it's own, but can be converted to a type if there's target-typed
                // conditional expressions in play. For example:
                //
                //     int? x = conditional ? (int?)trueValue : null;
                //
                // Once you remove the cast, the conditional has no type itself, but is being converted to int? by a conditional
                // expression conversion.
                if (newExpressionType == null &&
                    this.SpeculativeSemanticModel.GetConversion(newExpression, this.CancellationToken).IsConditionalExpression)
                {
                    newExpressionType = newExpressionTypeInfo.ConvertedType;
                }
 
                if (originalExpressionType == null || newExpressionType == null)
                {
                    // With the current implementation of the C# binder, this is impossible, but it's probably not wise to
                    // depend on an implementation detail of another layer.
                    return originalExpressionType != newExpressionType;
                }
 
                var originalConversion = this.OriginalSemanticModel.ClassifyConversion(originalOtherPartOfConditional, originalExpressionType);
                var newConversion = this.SpeculativeSemanticModel.ClassifyConversion(newOtherPartOfConditional, newExpressionType);
 
                // If this changes a boxing operation in one of the branches, we assume that semantics will change.
                if (originalConversion.IsBoxing != newConversion.IsBoxing)
                {
                    return true;
                }
 
                if (ReplacementBreaksBoxingInConditionalExpression(originalExpressionTypeInfo, newExpressionTypeInfo, (ExpressionSyntax)previousOriginalNode, (ExpressionSyntax)previousReplacedNode))
                {
                    return true;
                }
 
                if (!ConversionsAreCompatible(originalConversion, newConversion))
                {
                    return true;
                }
            }
        }
        else if (currentOriginalNode is CaseSwitchLabelSyntax originalCaseSwitchLabel)
        {
            var newCaseSwitchLabel = (CaseSwitchLabelSyntax)currentReplacedNode;
 
            // If case label is changing, then need to check if the semantics will change for the switch expression.  
            // e.g. if switch expression is "switch(x)" where "object x = 1f", then "case 1:" and "case (float) 1:" are different.
            var originalCaseType = this.OriginalSemanticModel.GetTypeInfo(previousOriginalNode, this.CancellationToken).Type;
            var newCaseType = this.SpeculativeSemanticModel.GetTypeInfo(previousReplacedNode, this.CancellationToken).Type;
 
            if (Equals(originalCaseType, newCaseType))
                return false;
 
            var oldSwitchStatement = (SwitchStatementSyntax)originalCaseSwitchLabel.Parent.Parent;
            var newSwitchStatement = (SwitchStatementSyntax)newCaseSwitchLabel.Parent.Parent;
 
            var originalConversion = this.OriginalSemanticModel.ClassifyConversion(oldSwitchStatement.Expression, originalCaseType);
            var newConversion = this.SpeculativeSemanticModel.ClassifyConversion(newSwitchStatement.Expression, newCaseType);
 
            // if conversion only exists for either original or new, then semantics changed.
            if (originalConversion.Exists != newConversion.Exists)
            {
                return true;
            }
 
            // Same conversion cannot result in both originalCaseType and newCaseType, which means the semantics changed
            // (since originalCaseType != newCaseType)
            return originalConversion == newConversion;
        }
        else if (currentOriginalNode is SwitchStatementSyntax originalSwitchStatement &&
                 originalSwitchStatement.Expression == previousOriginalNode)
        {
            // Switch statement's expression changed, verify that the conversions from switch case labels to new switch
            // expression type are not broken.
 
            var newSwitchStatement = (SwitchStatementSyntax)currentReplacedNode;
            var previousReplacedExpression = (ExpressionSyntax)previousReplacedNode;
 
            // it is never legal to use `default/null` in a switch statement's expression.
            if (previousReplacedExpression.WalkDownParentheses().Kind() is SyntaxKind.NullLiteralExpression or SyntaxKind.DefaultLiteralExpression)
                return true;
 
            var originalSwitchLabels = originalSwitchStatement.Sections.SelectMany(section => section.Labels).ToArray();
            var newSwitchLabels = newSwitchStatement.Sections.SelectMany(section => section.Labels).ToArray();
 
            for (var i = 0; i < originalSwitchLabels.Length; i++)
            {
                if (originalSwitchLabels[i] is CaseSwitchLabelSyntax originalSwitchLabel &&
                    newSwitchLabels[i] is CaseSwitchLabelSyntax newSwitchLabel &&
                    !ImplicitConversionsAreCompatible(originalSwitchLabel.Value, newSwitchLabel.Value))
                {
                    return true;
                }
            }
        }
        else if (currentOriginalNode is SwitchExpressionSyntax originalSwitchExpression &&
                 originalSwitchExpression.GoverningExpression == previousOriginalNode)
        {
            var replacedSwitchExpression = (SwitchExpressionSyntax)currentReplacedNode;
 
            // Switch expression's expression changed.  Ensure it's the same type as before. If not, inference of
            // the meaning of the patterns within can change.
 
            var originalExprType = this.OriginalSemanticModel.GetTypeInfo(originalSwitchExpression.GoverningExpression, CancellationToken);
            var replacedExprType = this.SpeculativeSemanticModel.GetTypeInfo(replacedSwitchExpression.GoverningExpression, CancellationToken);
 
            if (!Equals(originalExprType.Type, replacedExprType.Type))
                return true;
        }
        else if (currentOriginalNode is IfStatementSyntax originalIfStatement)
        {
            var newIfStatement = (IfStatementSyntax)currentReplacedNode;
 
            if (originalIfStatement.Condition == previousOriginalNode)
            {
                // If condition changed, verify that original and replaced expression types are compatible.
                if (!TypesAreCompatible(originalIfStatement.Condition, newIfStatement.Condition))
                {
                    return true;
                }
            }
        }
        else if (currentOriginalNode is ConstructorInitializerSyntax originalCtorInitializer)
        {
            var newCtorInitializer = (ConstructorInitializerSyntax)currentReplacedNode;
            return ReplacementBreaksConstructorInitializer(originalCtorInitializer, newCtorInitializer);
        }
        else if (currentOriginalNode.Kind() == SyntaxKind.CollectionInitializerExpression)
        {
            return previousOriginalNode != null &&
                ReplacementBreaksCollectionInitializerAddMethod((ExpressionSyntax)previousOriginalNode, (ExpressionSyntax)previousReplacedNode);
        }
        else if (currentOriginalNode.Kind() == SyntaxKind.ImplicitArrayCreationExpression)
        {
            return !TypesAreCompatible((ExpressionSyntax)currentOriginalNode, (ExpressionSyntax)currentReplacedNode);
        }
        else if (currentOriginalNode is AnonymousObjectMemberDeclaratorSyntax originalAnonymousObjectMemberDeclarator)
        {
            var replacedAnonymousObjectMemberDeclarator = (AnonymousObjectMemberDeclaratorSyntax)currentReplacedNode;
            return ReplacementBreaksAnonymousObjectMemberDeclarator(originalAnonymousObjectMemberDeclarator, replacedAnonymousObjectMemberDeclarator);
        }
        else if (currentOriginalNode.Kind() == SyntaxKind.DefaultExpression)
        {
            return !TypesAreCompatible((ExpressionSyntax)currentOriginalNode, (ExpressionSyntax)currentReplacedNode);
        }
 
        return false;
    }
 
    /// <summary>
    /// Checks if the conversion might change the resultant boxed type.
    /// Similar boxing checks are performed elsewhere, but in this case we need to perform the check on the entire conditional expression.
    /// This will make sure the resultant cast is proper for the type of the conditional expression.
    /// </summary>
    private bool ReplacementBreaksBoxingInConditionalExpression(TypeInfo originalExpressionTypeInfo, TypeInfo newExpressionTypeInfo, ExpressionSyntax previousOriginalNode, ExpressionSyntax previousReplacedNode)
    {
        // If the resultant types are different and it is boxing to the converted type then semantics could be changing.
        if (!Equals(originalExpressionTypeInfo.Type, newExpressionTypeInfo.Type))
        {
            var originalConvertedTypeConversion = this.OriginalSemanticModel.ClassifyConversion(previousOriginalNode, originalExpressionTypeInfo.ConvertedType);
            var newExpressionConvertedTypeConversion = this.SpeculativeSemanticModel.ClassifyConversion(previousReplacedNode, newExpressionTypeInfo.ConvertedType);
 
            if (originalConvertedTypeConversion.IsBoxing && newExpressionConvertedTypeConversion.IsBoxing)
            {
                return true;
            }
        }
 
        return false;
    }
 
    private bool ReplacementBreaksAnonymousObjectMemberDeclarator(AnonymousObjectMemberDeclaratorSyntax originalAnonymousObjectMemberDeclarator, AnonymousObjectMemberDeclaratorSyntax replacedAnonymousObjectMemberDeclarator)
    {
        var originalExpressionType = this.OriginalSemanticModel.GetTypeInfo(originalAnonymousObjectMemberDeclarator.Expression, this.CancellationToken).Type;
        var newExpressionType = this.SpeculativeSemanticModel.GetTypeInfo(replacedAnonymousObjectMemberDeclarator.Expression, this.CancellationToken).Type;
        return !object.Equals(originalExpressionType, newExpressionType);
    }
 
    private bool ReplacementBreaksConstructorInitializer(ConstructorInitializerSyntax ctorInitializer, ConstructorInitializerSyntax newCtorInitializer)
    {
        var originalSymbol = this.OriginalSemanticModel.GetSymbolInfo(ctorInitializer, CancellationToken).Symbol;
        var newSymbol = this.SpeculativeSemanticModel.GetSymbolInfo(newCtorInitializer, CancellationToken).Symbol;
        return !SymbolsAreCompatible(originalSymbol, newSymbol);
    }
 
    private bool ReplacementBreaksCollectionInitializerAddMethod(ExpressionSyntax originalInitializer, ExpressionSyntax newInitializer)
    {
        var originalSymbol = this.OriginalSemanticModel.GetCollectionInitializerSymbolInfo(originalInitializer, CancellationToken).Symbol;
        var newSymbol = this.SpeculativeSemanticModel.GetCollectionInitializerSymbolInfo(newInitializer, CancellationToken).Symbol;
        return !SymbolsAreCompatible(originalSymbol, newSymbol);
    }
 
    protected override bool ExpressionMightReferenceMember(SyntaxNode node)
        => node.Kind() is
            SyntaxKind.InvocationExpression or
            SyntaxKind.ElementAccessExpression or
            SyntaxKind.SimpleMemberAccessExpression or
            SyntaxKind.ImplicitElementAccess or
            SyntaxKind.ObjectCreationExpression;
 
    protected override ImmutableArray<ArgumentSyntax> GetArguments(ExpressionSyntax expression)
    {
        var argumentsList = GetArgumentList(expression);
        return argumentsList != null
            ? argumentsList.Arguments.AsImmutableOrEmpty()
            : default;
    }
 
    private static BaseArgumentListSyntax GetArgumentList(ExpressionSyntax expression)
    {
        expression = expression.WalkDownParentheses();
 
        return expression.Kind() switch
        {
            SyntaxKind.InvocationExpression => ((InvocationExpressionSyntax)expression).ArgumentList,
            SyntaxKind.ObjectCreationExpression => ((ObjectCreationExpressionSyntax)expression).ArgumentList,
            SyntaxKind.ElementAccessExpression => ((ElementAccessExpressionSyntax)expression).ArgumentList,
            _ => null,
        };
    }
 
    protected override ExpressionSyntax GetReceiver(ExpressionSyntax expression)
    {
        expression = expression.WalkDownParentheses();
 
        switch (expression.Kind())
        {
            case SyntaxKind.SimpleMemberAccessExpression:
                return ((MemberAccessExpressionSyntax)expression).Expression;
 
            case SyntaxKind.InvocationExpression:
                {
                    var result = ((InvocationExpressionSyntax)expression).Expression;
                    if (result.IsKind(SyntaxKind.SimpleMemberAccessExpression))
                    {
                        return GetReceiver(result);
                    }
 
                    return result;
                }
 
            case SyntaxKind.ElementAccessExpression:
                {
                    var result = ((ElementAccessExpressionSyntax)expression).Expression;
                    if (result.IsKind(SyntaxKind.SimpleMemberAccessExpression))
                    {
                        result = GetReceiver(result);
                    }
 
                    return result;
                }
 
            default:
                return null;
        }
    }
 
    protected override bool IsInNamespaceOrTypeContext(ExpressionSyntax node)
        => SyntaxFacts.IsInNamespaceOrTypeContext(node);
 
    protected override ExpressionSyntax GetForEachStatementExpression(CommonForEachStatementSyntax forEachStatement)
        => forEachStatement.Expression;
 
    protected override ExpressionSyntax GetThrowStatementExpression(ThrowStatementSyntax throwStatement)
        => throwStatement.Expression;
 
    protected override bool IsForEachTypeInferred(CommonForEachStatementSyntax forEachStatement, SemanticModel semanticModel)
        => forEachStatement switch
        {
            ForEachStatementSyntax foreachStatement => foreachStatement.Type.IsTypeInferred(semanticModel),
            ForEachVariableStatementSyntax { Variable: DeclarationExpressionSyntax declarationExpression } => declarationExpression.Type.IsTypeInferred(semanticModel),
            _ => false,
        };
 
    protected override bool IsParenthesizedExpression(SyntaxNode node)
        => node.IsKind(SyntaxKind.ParenthesizedExpression);
 
    protected override bool IsNamedArgument(ArgumentSyntax argument)
        => argument.NameColon != null && !argument.NameColon.IsMissing;
 
    protected override string GetNamedArgumentIdentifierValueText(ArgumentSyntax argument)
        => argument.NameColon.Name.Identifier.ValueText;
 
    private bool ReplacementBreaksBinaryExpression(BinaryExpressionSyntax binaryExpression, BinaryExpressionSyntax newBinaryExpression)
    {
        if (binaryExpression.Kind() is SyntaxKind.AsExpression or SyntaxKind.IsExpression &&
            ReplacementBreaksIsOrAsExpression(binaryExpression, newBinaryExpression))
        {
            return true;
        }
 
        return !SymbolsAreCompatible(binaryExpression, newBinaryExpression) ||
            !TypesAreCompatible(binaryExpression, newBinaryExpression) ||
            !ImplicitConversionsAreCompatible(binaryExpression, newBinaryExpression);
    }
 
    private bool ReplacementBreaksConditionalAccessExpression(ConditionalAccessExpressionSyntax conditionalAccessExpression, ConditionalAccessExpressionSyntax newConditionalAccessExpression)
    {
        return !SymbolsAreCompatible(conditionalAccessExpression, newConditionalAccessExpression) ||
            !TypesAreCompatible(conditionalAccessExpression, newConditionalAccessExpression) ||
            !SymbolsAreCompatible(conditionalAccessExpression.WhenNotNull, newConditionalAccessExpression.WhenNotNull) ||
            !TypesAreCompatible(conditionalAccessExpression.WhenNotNull, newConditionalAccessExpression.WhenNotNull);
    }
 
    private bool ReplacementBreaksIsOrAsExpression(BinaryExpressionSyntax originalIsOrAsExpression, BinaryExpressionSyntax newIsOrAsExpression)
    {
        // Special case: Lambda expressions and anonymous delegates cannot appear
        // on the left-side of an 'is' or 'as' cast. We can handle this case syntactically.
        if (originalIsOrAsExpression.Left.WalkDownParentheses() is not AnonymousFunctionExpressionSyntax &&
            newIsOrAsExpression.Left.WalkDownParentheses() is AnonymousFunctionExpressionSyntax)
        {
            return true;
        }
 
        var originalConvertedType = this.OriginalSemanticModel.GetTypeInfo(originalIsOrAsExpression.Right).Type;
        var newConvertedType = this.SpeculativeSemanticModel.GetTypeInfo(newIsOrAsExpression.Right).Type;
 
        if (originalConvertedType == null || newConvertedType == null)
        {
            return originalConvertedType != newConvertedType;
        }
 
        var originalConversion = this.OriginalSemanticModel.ClassifyConversion(originalIsOrAsExpression.Left, originalConvertedType, isExplicitInSource: true);
        var newConversion = this.SpeculativeSemanticModel.ClassifyConversion(newIsOrAsExpression.Left, newConvertedType, isExplicitInSource: true);
 
        // Is and As operators do not consider any user-defined operators, just ensure that the conversion exists.
        return originalConversion.Exists != newConversion.Exists;
    }
 
    private bool ReplacementBreaksAssignmentExpression(AssignmentExpressionSyntax assignmentExpression, AssignmentExpressionSyntax newAssignmentExpression)
    {
        if (assignmentExpression.IsCompoundAssignExpression() &&
            assignmentExpression.Kind() != SyntaxKind.LeftShiftAssignmentExpression &&
            assignmentExpression.Kind() != SyntaxKind.RightShiftAssignmentExpression &&
            ReplacementBreaksCompoundAssignment(assignmentExpression.Left, assignmentExpression.Right, newAssignmentExpression.Left, newAssignmentExpression.Right))
        {
            return true;
        }
 
        return !SymbolsAreCompatible(assignmentExpression, newAssignmentExpression) ||
            !TypesAreCompatible(assignmentExpression, newAssignmentExpression) ||
            !ImplicitConversionsAreCompatible(assignmentExpression, newAssignmentExpression);
    }
 
    private bool ReplacementBreaksQueryClause(QueryClauseSyntax originalClause, QueryClauseSyntax newClause)
    {
        // Ensure QueryClauseInfos are compatible.
        var originalClauseInfo = this.OriginalSemanticModel.GetQueryClauseInfo(originalClause, this.CancellationToken);
        var newClauseInfo = this.SpeculativeSemanticModel.GetQueryClauseInfo(newClause, this.CancellationToken);
 
        return !SymbolInfosAreCompatible(originalClauseInfo.CastInfo, newClauseInfo.CastInfo) ||
            !SymbolInfosAreCompatible(originalClauseInfo.OperationInfo, newClauseInfo.OperationInfo);
    }
 
    protected override bool ReplacementIntroducesDisallowedNullType(
        ExpressionSyntax originalExpression,
        ExpressionSyntax newExpression,
        TypeInfo originalTypeInfo,
        TypeInfo newTypeInfo)
    {
        // If the base check is fine with the nullability of types before/after, then we're good and there are no
        // more checks we need to do.
        if (!base.ReplacementIntroducesDisallowedNullType(originalExpression, newExpression, originalTypeInfo, newTypeInfo))
            return false;
 
        // If, however, the base check is not ok.  That means that the old expression had an initial type, but the
        // new expression does not.  This may or may not be ok depending on the construct.  If it's a supported
        // construct then we want to check the new constructs converted type against the old construct's original
        // type to make sure those still match.  If so, this change is fine.
        if (IsSupportedConstructWithNullType() &&
            SymbolsAreCompatible(originalTypeInfo.Type, newTypeInfo.ConvertedType))
        {
            return false;
        }
 
        return true;
 
        bool IsSupportedConstructWithNullType()
        {
            // A conditional expression may become untyped if it now involves a conditional conversion.  For example:
            //
            //      int? s = x ? 0 : null;
            //
            // In this case, the null type is allowed if we do have a conditional-expression-conversion *and* the
            // converted type matches the original type.
            if (newExpression.IsKind(SyntaxKind.ConditionalExpression) &&
                ConditionalExpressionConversionsAreAllowed(newExpression) &&
                this.SpeculativeSemanticModel.GetConversion(newExpression).IsConditionalExpression)
            {
                return true;
            }
 
            // Similar to above, it's fine for a switch expression to potentially change to having a 'null' direct type
            // (as long as a target-typed switch-expression conversion happened).  Note: unlike above, we don't have to
            // check a language version since switch expressions always supported target-typed conversion.
            if (newExpression.IsKind(SyntaxKind.SwitchExpression) &&
                this.SpeculativeSemanticModel.GetConversion(newExpression).IsSwitchExpression &&
                SymbolsAreCompatible(originalTypeInfo.Type, newTypeInfo.ConvertedType))
            {
                return true;
            }
 
            // Similar to above, it's fine for a collection expression to have a 'null' direct type (as long as a
            // target-typed collection-expression conversion happened).  Note: unlike above, we don't have to check
            // a language version since collection expressions always supported collection-expression-conversions.
            if (newExpression.IsKind(SyntaxKind.CollectionExpression) &&
                this.SpeculativeSemanticModel.GetConversion(newExpression).IsCollectionExpression)
            {
                return true;
            }
 
            // Similar to above, it's fine for a tuple expression to have a 'null' direct type (as long as a
            // target-typed tuple-expression conversion happened).  Note: unlike above, we don't have to check
            // a language version since tuple expressions always supported tuple-expression-conversions.
            if (newExpression.IsKind(SyntaxKind.TupleExpression) &&
                this.SpeculativeSemanticModel.GetConversion(newExpression).IsTupleLiteralConversion &&
                SymbolsAreCompatible(originalTypeInfo.Type, newTypeInfo.ConvertedType))
            {
                return true;
            }
 
            return false;
        }
    }
 
    protected override bool ConversionsAreCompatible(SemanticModel originalModel, ExpressionSyntax originalExpression, SemanticModel newModel, ExpressionSyntax newExpression)
    {
        var originalConversion = originalModel.GetConversion(originalExpression);
        var newConversion = newModel.GetConversion(newExpression);
 
        if (originalExpression.IsKind(SyntaxKind.ConditionalExpression) &&
            newExpression.IsKind(SyntaxKind.ConditionalExpression))
        {
            if (newConversion.IsConditionalExpression)
            {
                // If we went from a non-conditional-conversion to a conditional-conversion (i.e. by removing a
                // cast), then that is always an error before CSharp9, and should not be allowed.
                if (!originalConversion.IsConditionalExpression && !ConditionalExpressionConversionsAreAllowed(originalExpression))
                    return false;
 
                // If the only change to the conversion here is the introduction of a conditional expression conversion,
                // that means types didn't really change in a meaningful way.
                if (originalConversion.IsIdentity)
                    return true;
            }
        }
 
        return ConversionsAreCompatible(originalConversion, newConversion);
    }
 
    private static bool ConditionalExpressionConversionsAreAllowed(ExpressionSyntax originalExpression)
        => originalExpression.GetLanguageVersion() >= LanguageVersion.CSharp9;
 
    protected override bool ConversionsAreCompatible(ExpressionSyntax originalExpression, ITypeSymbol originalTargetType, ExpressionSyntax newExpression, ITypeSymbol newTargetType)
    {
        this.GetConversions(originalExpression, originalTargetType, newExpression, newTargetType, out var originalConversion, out var newConversion);
 
        if (originalConversion == null || newConversion == null)
        {
            return false;
        }
 
        return ConversionsAreCompatible(originalConversion.Value, newConversion.Value);
    }
 
    private bool ConversionsAreCompatible(Conversion originalConversion, Conversion newConversion)
    {
        if (originalConversion.Exists != newConversion.Exists ||
            (!originalConversion.IsExplicit && newConversion.IsExplicit))
        {
            return false;
        }
 
        var originalIsUserDefined = originalConversion.IsUserDefined;
        var newIsUserDefined = newConversion.IsUserDefined;
 
        if (originalIsUserDefined != newIsUserDefined)
        {
            return false;
        }
 
        if (originalIsUserDefined || originalConversion.MethodSymbol != null || newConversion.MethodSymbol != null)
        {
            return SymbolsAreCompatible(originalConversion.MethodSymbol, newConversion.MethodSymbol);
        }
 
        return true;
    }
 
    protected override bool ForEachConversionsAreCompatible(SemanticModel originalModel, CommonForEachStatementSyntax originalForEach, SemanticModel newModel, CommonForEachStatementSyntax newForEach)
    {
        var originalInfo = originalModel.GetForEachStatementInfo(originalForEach);
        var newInfo = newModel.GetForEachStatementInfo(newForEach);
        return ConversionsAreCompatible(originalInfo.CurrentConversion, newInfo.CurrentConversion)
            && ConversionsAreCompatible(originalInfo.ElementConversion, newInfo.ElementConversion);
    }
 
    protected override void GetForEachSymbols(
        SemanticModel model,
        CommonForEachStatementSyntax forEach,
        out IMethodSymbol getEnumeratorMethod,
        out ITypeSymbol elementType,
        out ImmutableArray<ILocalSymbol> localVariables)
    {
        var info = model.GetForEachStatementInfo(forEach);
        getEnumeratorMethod = info.GetEnumeratorMethod;
        elementType = info.ElementType;
 
        if (forEach is ForEachStatementSyntax foreachStatement)
        {
            localVariables = [(ILocalSymbol)model.GetRequiredDeclaredSymbol(foreachStatement, this.CancellationToken)];
        }
        else if (forEach is ForEachVariableStatementSyntax { Variable: DeclarationExpressionSyntax declarationExpression })
        {
            using var variables = TemporaryArray<ILocalSymbol>.Empty;
            AddVariables(declarationExpression.Designation, ref variables.AsRef());
 
            localVariables = variables.ToImmutableAndClear();
        }
        else
        {
            localVariables = [];
        }
 
        return;
 
        void AddVariables(VariableDesignationSyntax designation, ref TemporaryArray<ILocalSymbol> variables)
        {
            switch (designation)
            {
                case SingleVariableDesignationSyntax singleVariableDesignation:
                    variables.Add((ILocalSymbol)model.GetRequiredDeclaredSymbol(singleVariableDesignation, CancellationToken));
                    break;
 
                case ParenthesizedVariableDesignationSyntax parenthesizedVariableDesignation:
                    foreach (var child in parenthesizedVariableDesignation.Variables)
                        AddVariables(child, ref variables);
 
                    break;
            }
        }
    }
 
    protected override bool IsReferenceConversion(Compilation compilation, ITypeSymbol sourceType, ITypeSymbol targetType)
        => compilation.ClassifyConversion(sourceType, targetType).IsReference;
 
    protected override Conversion ClassifyConversion(SemanticModel model, ExpressionSyntax expression, ITypeSymbol targetType)
        => model.ClassifyConversion(expression, targetType);
 
    protected override Conversion ClassifyConversion(SemanticModel model, ITypeSymbol originalType, ITypeSymbol targetType)
        => model.Compilation.ClassifyConversion(originalType, targetType);
}