' 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.

Imports System.Collections.Immutable
Imports System.Threading
Imports Microsoft.CodeAnalysis.VisualBasic.LanguageService
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax

Namespace Microsoft.CodeAnalysis.VisualBasic.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>
    Friend Class SpeculationAnalyzer
        Inherits AbstractSpeculationAnalyzer(Of
            ExpressionSyntax,
            TypeSyntax,
            AttributeSyntax,
            ArgumentSyntax,
            ForEachStatementSyntax,
            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 Sub New(expression As ExpressionSyntax, newExpression As ExpressionSyntax, semanticModel As SemanticModel, cancellationToken As CancellationToken, Optional skipVerificationForReplacedNode As Boolean = False, Optional failOnOverloadResolutionFailuresInOriginalCode As Boolean = False)
            MyBase.New(expression, newExpression, semanticModel, cancellationToken, skipVerificationForReplacedNode, failOnOverloadResolutionFailuresInOriginalCode)
        End Sub

        Protected Overrides ReadOnly Property SyntaxFactsService As CodeAnalysis.LanguageService.ISyntaxFacts = VisualBasicSyntaxFacts.Instance
        Protected Overrides Function CanAccessInstanceMemberThrough(expression As ExpressionSyntax) As Boolean
            ' vb can reference an instance member by just writing `.X` (when in a 'with' block), or by writing Me.X,
            ' MyBase.X and MyClass.X (the latter is not just for accessing static members).
            Return expression Is Nothing OrElse expression.IsKind(SyntaxKind.MeExpression, SyntaxKind.MyBaseExpression, SyntaxKind.MyClassExpression)
        End Function

        Protected Overrides Function GetSemanticRootForSpeculation(expression As ExpressionSyntax) As SyntaxNode
            Debug.Assert(expression IsNot Nothing)

            Dim parentNodeToSpeculate = expression _
                .AncestorsAndSelf(ascendOutOfTrivia:=False) _
                .Where(Function(node) CanSpeculateOnNode(node)) _
                .LastOrDefault()

            If parentNodeToSpeculate Is Nothing Then
                parentNodeToSpeculate = expression
            End If

            Return parentNodeToSpeculate
        End Function

        Public Shared Function CanSpeculateOnNode(node As SyntaxNode) As Boolean
            Return TypeOf node Is ExecutableStatementSyntax OrElse
                TypeOf node Is TypeSyntax OrElse
                node.Kind = SyntaxKind.Attribute OrElse
                node.Kind = SyntaxKind.EqualsValue OrElse
                node.Kind = SyntaxKind.AsNewClause OrElse
                node.Kind = SyntaxKind.RangeArgument
        End Function

        Protected Overrides Function GetSemanticRootOfReplacedExpression(semanticRootOfOriginalExpr As SyntaxNode, annotatedReplacedExpression As ExpressionSyntax) As SyntaxNode
            Dim originalExpression = Me.OriginalExpression

            ' Speculation is not supported for AsNewClauseSyntax nodes.
            ' Generate an EqualsValueSyntax node with the inner NewExpression of the AsNewClauseSyntax node for speculation.
            If semanticRootOfOriginalExpr.Kind = SyntaxKind.AsNewClause Then
                ' Because the original expression will change identity in the newly generated EqualsValueSyntax node,
                ' we annotate it here to allow us to get back to it after replace.
                Dim originalExprAnnotation = New SyntaxAnnotation()
                Dim annotatedOriginalExpression = originalExpression.WithAdditionalAnnotations(originalExprAnnotation)
                semanticRootOfOriginalExpr = semanticRootOfOriginalExpr.ReplaceNode(originalExpression, annotatedOriginalExpression)

                Dim asNewClauseNode = DirectCast(semanticRootOfOriginalExpr, AsNewClauseSyntax)
                semanticRootOfOriginalExpr = SyntaxFactory.EqualsValue(asNewClauseNode.NewExpression)
                semanticRootOfOriginalExpr = asNewClauseNode.CopyAnnotationsTo(semanticRootOfOriginalExpr)
                originalExpression = DirectCast(semanticRootOfOriginalExpr.GetAnnotatedNodesAndTokens(originalExprAnnotation).Single().AsNode(), ExpressionSyntax)
            End If

            Return semanticRootOfOriginalExpr.ReplaceNode(originalExpression, annotatedReplacedExpression)
        End Function

        Protected Overrides Sub ValidateSpeculativeSemanticModel(speculativeSemanticModel As SemanticModel, nodeToSpeculate As SyntaxNode)
            Debug.Assert(speculativeSemanticModel IsNot Nothing OrElse
                        TypeOf nodeToSpeculate Is ExpressionSyntax OrElse
                        Me.SemanticRootOfOriginalExpression.GetAncestors().Any(Function(node) node.IsKind(SyntaxKind.IncompleteMember)),
                        "SemanticModel.TryGetSpeculativeSemanticModel() API returned false.")
        End Sub

        Protected Overrides Function CreateSpeculativeSemanticModel(originalNode As SyntaxNode, nodeToSpeculate As SyntaxNode, semanticModel As SemanticModel) As SemanticModel
            Return CreateSpeculativeSemanticModelForNode(originalNode, nodeToSpeculate, semanticModel)
        End Function

        Public Shared Function CreateSpeculativeSemanticModelForNode(originalNode As SyntaxNode, nodeToSpeculate As SyntaxNode, semanticModel As SemanticModel) As SemanticModel
            Dim position = originalNode.SpanStart
            Dim isInNamespaceOrTypeContext = SyntaxFacts.IsInNamespaceOrTypeContext(TryCast(originalNode, ExpressionSyntax))
            Return CreateSpeculativeSemanticModelForNode(nodeToSpeculate, semanticModel, position, isInNamespaceOrTypeContext)
        End Function

        Public Shared Function CreateSpeculativeSemanticModelForNode(nodeToSpeculate As SyntaxNode, semanticModel As SemanticModel, position As Integer, isInNamespaceOrTypeContext As Boolean) As SemanticModel
            If semanticModel.IsSpeculativeSemanticModel Then
                ' Chaining speculative model Not supported, speculate off the original model.
                Debug.Assert(semanticModel.ParentModel IsNot Nothing)
                Debug.Assert(Not semanticModel.ParentModel.IsSpeculativeSemanticModel)
                position = semanticModel.OriginalPositionForSpeculation
                semanticModel = semanticModel.ParentModel
            End If

            Dim speculativeModel As SemanticModel = Nothing
            Dim statementNode = TryCast(nodeToSpeculate, ExecutableStatementSyntax)
            If statementNode IsNot Nothing Then
                semanticModel.TryGetSpeculativeSemanticModel(position, statementNode, speculativeModel)
                Return speculativeModel
            End If

            Dim type = TryCast(nodeToSpeculate, TypeSyntax)
            If type IsNot Nothing Then
                Dim bindingOption = If(isInNamespaceOrTypeContext,
                                       SpeculativeBindingOption.BindAsTypeOrNamespace,
                                       SpeculativeBindingOption.BindAsExpression)
                semanticModel.TryGetSpeculativeSemanticModel(position, type, speculativeModel, bindingOption)
                Return speculativeModel
            End If

            Select Case nodeToSpeculate.Kind
                Case SyntaxKind.Attribute
                    semanticModel.TryGetSpeculativeSemanticModel(position, DirectCast(nodeToSpeculate, AttributeSyntax), speculativeModel)
                    Return speculativeModel

                Case SyntaxKind.EqualsValue
                    semanticModel.TryGetSpeculativeSemanticModel(position, DirectCast(nodeToSpeculate, EqualsValueSyntax), speculativeModel)
                    Return speculativeModel

                Case SyntaxKind.RangeArgument
                    semanticModel.TryGetSpeculativeSemanticModel(position, DirectCast(nodeToSpeculate, RangeArgumentSyntax), speculativeModel)
                    Return speculativeModel

                Case SyntaxKind.AsNewClause
                    ' Speculation is not supported for AsNewClauseSyntax nodes.
                    ' Generate an EqualsValueSyntax node with the inner NewExpression of the AsNewClauseSyntax node for speculation.

                    Dim asNewClauseNode = DirectCast(nodeToSpeculate, AsNewClauseSyntax)
                    nodeToSpeculate = SyntaxFactory.EqualsValue(asNewClauseNode.NewExpression)
                    nodeToSpeculate = asNewClauseNode.CopyAnnotationsTo(nodeToSpeculate)
                    semanticModel.TryGetSpeculativeSemanticModel(position, DirectCast(nodeToSpeculate, EqualsValueSyntax), speculativeModel)
                    Return speculativeModel
            End Select

            ' CONSIDER: Do we care about this case?
            Debug.Assert(TypeOf nodeToSpeculate Is ExpressionSyntax)
            Return Nothing
        End Function

#Region "Semantic comparison helpers"

        Private Function QuerySymbolsAreCompatible(originalNode As CollectionRangeVariableSyntax, newNode As CollectionRangeVariableSyntax) As Boolean
            Debug.Assert(originalNode IsNot Nothing)
            Debug.Assert(Me.SemanticRootOfOriginalExpression.DescendantNodesAndSelf().Contains(originalNode))
            Debug.Assert(newNode IsNot Nothing)
            Debug.Assert(Me.SemanticRootOfReplacedExpression.DescendantNodesAndSelf().Contains(newNode))

            Dim originalSymbolInfo = Me.OriginalSemanticModel.GetCollectionRangeVariableSymbolInfo(originalNode)
            Dim newSymbolInfo = Me.SpeculativeSemanticModel.GetCollectionRangeVariableSymbolInfo(newNode)
            Return SymbolInfosAreCompatible(originalSymbolInfo, newSymbolInfo)
        End Function

        Private Function QuerySymbolsAreCompatible(originalNode As AggregateClauseSyntax, newNode As AggregateClauseSyntax) As Boolean
            Debug.Assert(originalNode IsNot Nothing)
            Debug.Assert(Me.SemanticRootOfOriginalExpression.DescendantNodesAndSelf().Contains(originalNode))
            Debug.Assert(newNode IsNot Nothing)
            Debug.Assert(Me.SemanticRootOfReplacedExpression.DescendantNodesAndSelf().Contains(newNode))

            Dim originalSymbolInfo = Me.OriginalSemanticModel.GetAggregateClauseSymbolInfo(originalNode)
            Dim newSymbolInfo = Me.SpeculativeSemanticModel.GetAggregateClauseSymbolInfo(newNode)
            Return SymbolInfosAreCompatible(originalSymbolInfo, newSymbolInfo)
        End Function

        Private Function QuerySymbolsAreCompatible(originalNode As ExpressionRangeVariableSyntax, newNode As ExpressionRangeVariableSyntax) As Boolean
            Debug.Assert(originalNode IsNot Nothing)
            Debug.Assert(Me.SemanticRootOfOriginalExpression.DescendantNodesAndSelf().Contains(originalNode))
            Debug.Assert(newNode IsNot Nothing)
            Debug.Assert(Me.SemanticRootOfReplacedExpression.DescendantNodesAndSelf().Contains(newNode))

            Dim originalSymbolInfo = Me.OriginalSemanticModel.GetSymbolInfo(originalNode)
            Dim newSymbolInfo = Me.SpeculativeSemanticModel.GetSymbolInfo(newNode)
            Return SymbolInfosAreCompatible(originalSymbolInfo, newSymbolInfo)
        End Function

        Private Function QuerySymbolsAreCompatible(originalNode As OrderingSyntax, newNode As OrderingSyntax) As Boolean
            Debug.Assert(originalNode IsNot Nothing)
            Debug.Assert(Me.SemanticRootOfOriginalExpression.DescendantNodesAndSelf().Contains(originalNode))
            Debug.Assert(newNode IsNot Nothing)
            Debug.Assert(Me.SemanticRootOfReplacedExpression.DescendantNodesAndSelf().Contains(newNode))

            Dim originalSymbolInfo = Me.OriginalSemanticModel.GetSymbolInfo(originalNode)
            Dim newSymbolInfo = Me.SpeculativeSemanticModel.GetSymbolInfo(newNode)
            Return SymbolInfosAreCompatible(originalSymbolInfo, newSymbolInfo)
        End Function

        Private Function QuerySymbolsAreCompatible(originalNode As QueryClauseSyntax, newNode As QueryClauseSyntax) As Boolean
            Debug.Assert(originalNode IsNot Nothing)
            Debug.Assert(Me.SemanticRootOfOriginalExpression.DescendantNodesAndSelf().Contains(originalNode))
            Debug.Assert(newNode IsNot Nothing)
            Debug.Assert(Me.SemanticRootOfReplacedExpression.DescendantNodesAndSelf().Contains(newNode))

            Dim originalSymbolInfo = Me.OriginalSemanticModel.GetSymbolInfo(originalNode)
            Dim newSymbolInfo = Me.SpeculativeSemanticModel.GetSymbolInfo(newNode)
            Return SymbolInfosAreCompatible(originalSymbolInfo, newSymbolInfo)
        End Function

        Private Overloads Function SymbolInfosAreCompatible(originalSymbolInfo As CollectionRangeVariableSymbolInfo, newSymbolInfo As CollectionRangeVariableSymbolInfo) As Boolean
            Return SymbolInfosAreCompatible(originalSymbolInfo.ToQueryableCollectionConversion, newSymbolInfo.ToQueryableCollectionConversion) AndAlso
                SymbolInfosAreCompatible(originalSymbolInfo.AsClauseConversion, newSymbolInfo.AsClauseConversion) AndAlso
                SymbolInfosAreCompatible(originalSymbolInfo.SelectMany, newSymbolInfo.SelectMany)
        End Function

        Private Overloads Function SymbolInfosAreCompatible(originalSymbolInfo As AggregateClauseSymbolInfo, newSymbolInfo As AggregateClauseSymbolInfo) As Boolean
            Return SymbolInfosAreCompatible(originalSymbolInfo.Select1, newSymbolInfo.Select1) AndAlso
                SymbolInfosAreCompatible(originalSymbolInfo.Select2, newSymbolInfo.Select2)
        End Function
#End Region

        ''' <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 Function ReplacementChangesSemanticsOfUnchangedLambda(originalLambda As ExpressionSyntax, replacedLambda As ExpressionSyntax) As Boolean
            originalLambda = originalLambda.WalkDownParentheses()
            replacedLambda = replacedLambda.WalkDownParentheses()

            Dim originalLambdaBody As SyntaxNode, replacedLambdaBody As SyntaxNode
            Dim originalParams As SeparatedSyntaxList(Of ParameterSyntax), replacedParams As SeparatedSyntaxList(Of ParameterSyntax)

            Select Case originalLambda.Kind
                Case SyntaxKind.SingleLineFunctionLambdaExpression, SyntaxKind.SingleLineSubLambdaExpression
                    Dim originalSingleLineLambda = DirectCast(originalLambda, SingleLineLambdaExpressionSyntax)
                    Dim replacedSingleLineLambda = DirectCast(replacedLambda, SingleLineLambdaExpressionSyntax)

                    originalParams = originalSingleLineLambda.SubOrFunctionHeader.ParameterList.Parameters
                    replacedParams = replacedSingleLineLambda.SubOrFunctionHeader.ParameterList.Parameters
                    originalLambdaBody = originalSingleLineLambda.Body
                    replacedLambdaBody = replacedSingleLineLambda.Body

                Case SyntaxKind.MultiLineFunctionLambdaExpression, SyntaxKind.MultiLineSubLambdaExpression
                    Dim originalMultiLineLambda = DirectCast(originalLambda, MultiLineLambdaExpressionSyntax)
                    Dim replacedMultiLineLambda = DirectCast(replacedLambda, MultiLineLambdaExpressionSyntax)

                    originalParams = originalMultiLineLambda.SubOrFunctionHeader.ParameterList.Parameters
                    replacedParams = replacedMultiLineLambda.SubOrFunctionHeader.ParameterList.Parameters
                    originalLambdaBody = originalMultiLineLambda
                    replacedLambdaBody = replacedMultiLineLambda

                Case Else

                    Throw ExceptionUtilities.UnexpectedValue(originalLambda.Kind)
            End Select

            Debug.Assert(originalParams.Count = replacedParams.Count)

            If Not originalParams.Any() Then
                Return False
            End If

            Dim paramNames = New List(Of String)()
            For i As Integer = 0 To originalParams.Count - 1
                Dim originalParam = originalParams(i)
                Dim replacedParam = replacedParams(i)
                If Not HaveSameParameterType(originalParam, replacedParam) Then
                    paramNames.Add(originalParam.Identifier.Identifier.ValueText)
                End If
            Next

            If Not paramNames.Any() Then
                Return False
            End If

            Dim originalIdentifierNodes = originalLambdaBody _
                                          .DescendantNodes() _
                                          .OfType(Of IdentifierNameSyntax)() _
                                          .Where(Function(node) paramNames.Contains(node.Identifier.ValueText))
            If Not originalIdentifierNodes.Any() Then
                Return False
            End If

            Dim replacedIdentifierNodes = replacedLambdaBody _
                                          .DescendantNodes() _
                                          .OfType(Of IdentifierNameSyntax)() _
                                          .Where(Function(node) paramNames.Contains(node.Identifier.ValueText))
            Return ReplacementChangesSemanticsForNodes(originalIdentifierNodes, replacedIdentifierNodes, originalLambdaBody)
        End Function

        Private Function HaveSameParameterType(originalParam As ParameterSyntax, replacedParam As ParameterSyntax) As Boolean
            Dim originalParamType = Me.OriginalSemanticModel.GetDeclaredSymbol(originalParam).Type
            Dim replacedParamType = Me.SpeculativeSemanticModel.GetDeclaredSymbol(replacedParam).Type
            Return Equals(originalParamType, replacedParamType)
        End Function

        Private Function ReplacementChangesSemanticsForNodes(
            originalIdentifierNodes As IEnumerable(Of IdentifierNameSyntax),
            replacedIdentifierNodes As IEnumerable(Of IdentifierNameSyntax),
            originalRoot As SyntaxNode) As Boolean

            Debug.Assert(originalIdentifierNodes.Any())
            Debug.Assert(originalIdentifierNodes.Count() = replacedIdentifierNodes.Count())

            Dim originalChildNodeEnum = originalIdentifierNodes.GetEnumerator()
            Dim replacedChildNodeEnum = replacedIdentifierNodes.GetEnumerator()

            While originalChildNodeEnum.MoveNext()
                replacedChildNodeEnum.MoveNext()
                If ReplacementChangesSemantics(originalChildNodeEnum.Current, replacedChildNodeEnum.Current, originalRoot, skipVerificationForCurrentNode:=True) Then
                    Return True
                End If
            End While

            Return False
        End Function

        Protected Overrides Function ReplacementChangesSemanticsForNodeLanguageSpecific(currentOriginalNode As SyntaxNode, currentReplacedNode As SyntaxNode, previousOriginalNode As SyntaxNode, previousReplacedNode As SyntaxNode) As Boolean
            Debug.Assert(previousOriginalNode Is Nothing OrElse previousOriginalNode.Parent Is currentOriginalNode)
            Debug.Assert(previousReplacedNode Is Nothing OrElse previousReplacedNode.Parent Is currentReplacedNode)

            If TypeOf currentOriginalNode Is BinaryExpressionSyntax Then
                ' If replacing the node will result in a broken binary expression, we won't remove it.
                Dim originalExpression = DirectCast(currentOriginalNode, BinaryExpressionSyntax)
                Dim newExpression = DirectCast(currentReplacedNode, BinaryExpressionSyntax)
                If ReplacementBreaksBinaryExpression(originalExpression, newExpression) Then
                    Return True
                End If

                Return Not ImplicitConversionsAreCompatible(originalExpression, newExpression)
            ElseIf TypeOf currentOriginalNode Is AssignmentStatementSyntax Then
                Dim originalAssignmentStatement = DirectCast(currentOriginalNode, AssignmentStatementSyntax)

                If SyntaxFacts.IsAssignmentStatementOperatorToken(originalAssignmentStatement.OperatorToken.Kind()) Then
                    Dim newAssignmentStatement = DirectCast(currentReplacedNode, AssignmentStatementSyntax)

                    If ReplacementBreaksCompoundAssignment(originalAssignmentStatement.Left, originalAssignmentStatement.Right, newAssignmentStatement.Left, newAssignmentStatement.Right) Then
                        Return True
                    End If
                End If
            ElseIf currentOriginalNode.Kind = SyntaxKind.ConditionalAccessExpression Then
                Dim originalExpression = DirectCast(currentOriginalNode, ConditionalAccessExpressionSyntax)
                Dim newExpression = DirectCast(currentReplacedNode, ConditionalAccessExpressionSyntax)
                Return ReplacementBreaksConditionalAccessExpression(originalExpression, newExpression)
            ElseIf currentOriginalNode.Kind = SyntaxKind.VariableDeclarator Then
                ' 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.

                Dim originalDeclarator = DirectCast(currentOriginalNode, VariableDeclaratorSyntax)
                Dim newDeclarator = DirectCast(currentReplacedNode, VariableDeclaratorSyntax)
                If originalDeclarator.IsTypeInferred(Me.OriginalSemanticModel) AndAlso Not ConvertedTypesAreCompatible(originalDeclarator.Initializer.Value, newDeclarator.Initializer.Value) Then
                    Return True
                End If

                Return False
            ElseIf currentOriginalNode.Kind = SyntaxKind.CollectionInitializer Then
                Return _
                    previousOriginalNode IsNot Nothing AndAlso
                    ReplacementBreaksCollectionInitializerAddMethod(DirectCast(previousOriginalNode, ExpressionSyntax), DirectCast(previousReplacedNode, ExpressionSyntax))
            ElseIf currentOriginalNode.Kind = SyntaxKind.Interpolation Then
                Dim orignalInterpolation = DirectCast(currentOriginalNode, InterpolationSyntax)
                Dim newInterpolation = DirectCast(currentReplacedNode, InterpolationSyntax)

                Return ReplacementBreaksInterpolation(orignalInterpolation, newInterpolation)
            ElseIf currentOriginalNode.Kind = SyntaxKind.WithStatement Then
                Dim originalWithStatement = DirectCast(currentOriginalNode, WithStatementSyntax)
                Dim newWithStatement = DirectCast(currentReplacedNode, WithStatementSyntax)

                Return ReplacementBreaksWithStatement(originalWithStatement, newWithStatement)
            Else
                Dim originalCollectionRangeVariableSyntax = TryCast(currentOriginalNode, CollectionRangeVariableSyntax)
                If originalCollectionRangeVariableSyntax IsNot Nothing Then
                    Dim newCollectionRangeVariableSyntax = DirectCast(currentReplacedNode, CollectionRangeVariableSyntax)
                    Return Not QuerySymbolsAreCompatible(originalCollectionRangeVariableSyntax, newCollectionRangeVariableSyntax)
                End If

                Dim originalAggregateSyntax = TryCast(currentOriginalNode, AggregateClauseSyntax)
                If originalAggregateSyntax IsNot Nothing Then
                    Dim newAggregateSyntax = DirectCast(currentReplacedNode, AggregateClauseSyntax)
                    Return Not QuerySymbolsAreCompatible(originalAggregateSyntax, newAggregateSyntax)
                End If

                Dim originalExprRangeVariableSyntax = TryCast(currentOriginalNode, ExpressionRangeVariableSyntax)
                If originalExprRangeVariableSyntax IsNot Nothing Then
                    Dim newExprRangeVariableSyntax = DirectCast(currentReplacedNode, ExpressionRangeVariableSyntax)
                    Return Not QuerySymbolsAreCompatible(originalExprRangeVariableSyntax, newExprRangeVariableSyntax)
                End If

                Dim originalFunctionAggregationSyntax = TryCast(currentOriginalNode, FunctionAggregationSyntax)
                If originalFunctionAggregationSyntax IsNot Nothing Then
                    Dim newFunctionAggregationSyntax = DirectCast(currentReplacedNode, FunctionAggregationSyntax)
                    Return Not SymbolsAreCompatible(originalFunctionAggregationSyntax, newFunctionAggregationSyntax)
                End If

                Dim originalOrderingSyntax = TryCast(currentOriginalNode, OrderingSyntax)
                If originalOrderingSyntax IsNot Nothing Then
                    Dim newOrderingSyntax = DirectCast(currentReplacedNode, OrderingSyntax)
                    Return Not QuerySymbolsAreCompatible(originalOrderingSyntax, newOrderingSyntax)
                End If

                Dim originalQueryClauseSyntax = TryCast(currentOriginalNode, QueryClauseSyntax)
                If originalQueryClauseSyntax IsNot Nothing Then
                    Dim newQueryClauseSyntax = DirectCast(currentReplacedNode, QueryClauseSyntax)
                    Return Not QuerySymbolsAreCompatible(originalQueryClauseSyntax, newQueryClauseSyntax)
                End If
            End If

            Return False
        End Function

        Private Function ReplacementBreaksWithStatement(originalWithStatement As WithStatementSyntax, replacedWithStatement As WithStatementSyntax) As Boolean
            Dim originalTypeInfo = Me.OriginalSemanticModel.GetTypeInfo(originalWithStatement.Expression)
            Dim replacedTypeInfo = Me.SpeculativeSemanticModel.GetTypeInfo(replacedWithStatement.Expression)
            Return Not originalTypeInfo.Equals(replacedTypeInfo)
        End Function

        Private Function ReplacementBreaksCollectionInitializerAddMethod(originalInitializer As ExpressionSyntax, newInitializer As ExpressionSyntax) As Boolean
            Dim originalSymbol = Me.OriginalSemanticModel.GetCollectionInitializerSymbolInfo(originalInitializer, CancellationToken).Symbol
            Dim newSymbol = Me.SpeculativeSemanticModel.GetCollectionInitializerSymbolInfo(newInitializer, CancellationToken).Symbol
            Return Not SymbolsAreCompatible(originalSymbol, newSymbol)
        End Function

        Protected Overrides Function IsForEachTypeInferred(forEachStatement As ForEachStatementSyntax, semanticModel As SemanticModel) As Boolean
            Dim forEachControlVariable = TryCast(forEachStatement.ControlVariable, VariableDeclaratorSyntax)
            Return forEachControlVariable IsNot Nothing AndAlso forEachControlVariable.IsTypeInferred(Me.OriginalSemanticModel)
        End Function

        Protected Overrides Function ExpressionMightReferenceMember(node As SyntaxNode) As Boolean
            Return node.IsKind(SyntaxKind.InvocationExpression) OrElse
                node.IsKind(SyntaxKind.SimpleMemberAccessExpression)
        End Function

        Protected Overrides Function GetReceiver(expression As ExpressionSyntax) As ExpressionSyntax
            expression = expression.WalkDownParentheses()

            Select Case expression.Kind
                Case SyntaxKind.SimpleMemberAccessExpression
                    Return DirectCast(expression, MemberAccessExpressionSyntax).Expression.WalkDownParentheses()

                Case SyntaxKind.InvocationExpression
                    Dim result = DirectCast(expression, InvocationExpressionSyntax).Expression.WalkDownParentheses()
                    If result.IsKind(SyntaxKind.SimpleMemberAccessExpression) Then
                        Return GetReceiver(result)
                    End If

                    Return result

                Case Else
                    Return Nothing
            End Select
        End Function

        Protected Overrides Function IsNamedArgument(argument As ArgumentSyntax) As Boolean
            Return argument.IsNamed
        End Function

        Protected Overrides Function GetNamedArgumentIdentifierValueText(argument As ArgumentSyntax) As String
            Return DirectCast(argument, SimpleArgumentSyntax).NameColonEquals.Name.Identifier.ValueText
        End Function

        Protected Overrides Function GetArguments(expression As ExpressionSyntax) As ImmutableArray(Of ArgumentSyntax)
            Dim argumentList = GetArgumentList(expression)
            Return If(argumentList IsNot Nothing,
                      argumentList.Arguments.AsImmutable(),
                      Nothing)
        End Function

        Private Shared Function GetArgumentList(expression As ExpressionSyntax) As ArgumentListSyntax
            expression = expression.WalkDownParentheses()

            Select Case expression.Kind
                Case SyntaxKind.InvocationExpression
                    Return DirectCast(expression, InvocationExpressionSyntax).ArgumentList
                Case SyntaxKind.ObjectCreationExpression
                    Return DirectCast(expression, ObjectCreationExpressionSyntax).ArgumentList
                Case Else
                    Return Nothing
            End Select
        End Function

        Private Function ReplacementBreaksBinaryExpression(binaryExpression As BinaryExpressionSyntax, newBinaryExpression As BinaryExpressionSyntax) As Boolean
            Dim operatorTokenKind = binaryExpression.OperatorToken.Kind
            If SyntaxFacts.IsAssignmentStatementOperatorToken(operatorTokenKind) AndAlso
                operatorTokenKind <> SyntaxKind.LessThanLessThanEqualsToken AndAlso
                operatorTokenKind <> SyntaxKind.GreaterThanGreaterThanEqualsToken AndAlso
                ReplacementBreaksCompoundAssignment(binaryExpression.Left, binaryExpression.Right, newBinaryExpression.Left, newBinaryExpression.Right) Then
                Return True
            End If

            Return Not SymbolsAreCompatible(binaryExpression, newBinaryExpression) OrElse
                Not TypesAreCompatible(binaryExpression, newBinaryExpression)
        End Function

        Private Function ReplacementBreaksConditionalAccessExpression(conditionalAccessExpression As ConditionalAccessExpressionSyntax, newConditionalAccessExpression As ConditionalAccessExpressionSyntax) As Boolean
            Return _
                Not SymbolsAreCompatible(conditionalAccessExpression, newConditionalAccessExpression) OrElse
                Not TypesAreCompatible(conditionalAccessExpression, newConditionalAccessExpression) OrElse
                Not SymbolsAreCompatible(conditionalAccessExpression.WhenNotNull, newConditionalAccessExpression.WhenNotNull) OrElse
                Not TypesAreCompatible(conditionalAccessExpression.WhenNotNull, newConditionalAccessExpression.WhenNotNull)
        End Function

        Private Function ReplacementBreaksInterpolation(interpolation As InterpolationSyntax, newInterpolation As InterpolationSyntax) As Boolean
            Return Not TypesAreCompatible(interpolation.Expression, newInterpolation.Expression)
        End Function

        Protected Overrides Function GetForEachStatementExpression(forEachStatement As ForEachStatementSyntax) As ExpressionSyntax
            Return forEachStatement.Expression
        End Function

        Protected Overrides Function GetThrowStatementExpression(throwStatement As ThrowStatementSyntax) As ExpressionSyntax
            Return throwStatement.Expression
        End Function

        Protected Overrides Function IsInNamespaceOrTypeContext(node As ExpressionSyntax) As Boolean
            Return SyntaxFacts.IsInNamespaceOrTypeContext(node)
        End Function

        Protected Overrides Function IsParenthesizedExpression(node As SyntaxNode) As Boolean
            Return node.IsKind(SyntaxKind.ParenthesizedExpression)
        End Function

        Protected Overrides Function ConversionsAreCompatible(originalModel As SemanticModel, originalExpression As ExpressionSyntax, newModel As SemanticModel, newExpression As ExpressionSyntax) As Boolean
            Return ConversionsAreCompatible(originalModel.GetConversion(originalExpression), newModel.GetConversion(newExpression))
        End Function

        Protected Overrides Function ConversionsAreCompatible(originalExpression As ExpressionSyntax, originalTargetType As ITypeSymbol, newExpression As ExpressionSyntax, newTargetType As ITypeSymbol) As Boolean
            Dim originalConversion As Conversion?
            Dim newConversion As Conversion?

            Me.GetConversions(originalExpression, originalTargetType, newExpression, newTargetType, originalConversion, newConversion)

            If originalConversion Is Nothing OrElse newConversion Is Nothing Then
                Return False
            End If

            ' When Option Strict is not Off and the new expression has a constant value, it's possible that
            ' there Is a hidden narrowing conversion that will be missed. In that case, use the
            ' conversion between the type of the new expression and the new target type.

            If Me.OriginalSemanticModel.OptionStrict() <> OptionStrict.Off AndAlso
               Me.SpeculativeSemanticModel.GetConstantValue(newExpression).HasValue Then
                Dim newExpressionType = Me.SpeculativeSemanticModel.GetTypeInfo(newExpression).ConvertedType
                newConversion = Me.OriginalSemanticModel.Compilation.ClassifyConversion(newExpressionType, newTargetType)
            End If

            Return ConversionsAreCompatible(originalConversion.Value, newConversion.Value)
        End Function

        Private Overloads Function ConversionsAreCompatible(originalConversion As Conversion, newConversion As Conversion) As Boolean
            If originalConversion.Exists <> newConversion.Exists OrElse
                ((Not originalConversion.IsNarrowing) AndAlso newConversion.IsNarrowing) Then
                Return False
            End If

            Dim originalIsUserDefined = originalConversion.IsUserDefined
            Dim newIsUserDefined = newConversion.IsUserDefined

            If (originalIsUserDefined <> newIsUserDefined) Then
                Return False
            End If

            If (originalIsUserDefined OrElse originalConversion.MethodSymbol IsNot Nothing OrElse newConversion.MethodSymbol IsNot Nothing) Then
                Return SymbolsAreCompatible(originalConversion.MethodSymbol, newConversion.MethodSymbol)
            End If

            Return True
        End Function

        Protected Overrides Function ForEachConversionsAreCompatible(originalModel As SemanticModel, originalForEach As ForEachStatementSyntax, newModel As SemanticModel, newForEach As ForEachStatementSyntax) As Boolean
            Dim originalInfo = originalModel.GetForEachStatementInfo(originalForEach)
            Dim newInfo = newModel.GetForEachStatementInfo(newForEach)
            Return ConversionsAreCompatible(originalInfo.CurrentConversion, newInfo.CurrentConversion) AndAlso ConversionsAreCompatible(originalInfo.ElementConversion, newInfo.ElementConversion)
        End Function

        Protected Overrides Sub GetForEachSymbols(model As SemanticModel, forEach As ForEachStatementSyntax, ByRef getEnumeratorMethod As IMethodSymbol, ByRef elementType As ITypeSymbol)
            Dim info = model.GetForEachStatementInfo(forEach)
            getEnumeratorMethod = info.GetEnumeratorMethod
            elementType = info.ElementType
        End Sub

        Protected Overrides Function IsReferenceConversion(compilation As Compilation, sourceType As ITypeSymbol, targetType As ITypeSymbol) As Boolean
            Return compilation.ClassifyConversion(sourceType, targetType).IsReference
        End Function

        Protected Overrides Function ClassifyConversion(model As SemanticModel, expression As ExpressionSyntax, targetType As ITypeSymbol) As Conversion
            Return model.ClassifyConversion(expression, targetType)
        End Function

        Protected Overrides Function ClassifyConversion(model As SemanticModel, originalType As ITypeSymbol, targetType As ITypeSymbol) As Conversion
            Return model.Compilation.ClassifyConversion(originalType, targetType)
        End Function
    End Class
End Namespace
