File: CodeRefactorings\InlineTemporary\VisualBasicInlineTemporaryCodeRefactoringProvider.vb
Web Access
Project: src\src\Features\VisualBasic\Portable\Microsoft.CodeAnalysis.VisualBasic.Features.vbproj (Microsoft.CodeAnalysis.VisualBasic.Features)
' 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.Composition
Imports System.Diagnostics.CodeAnalysis
Imports System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.CodeActions
Imports Microsoft.CodeAnalysis.CodeRefactorings
Imports Microsoft.CodeAnalysis.FindSymbols
Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.InlineTemporary
Imports Microsoft.CodeAnalysis.Simplification
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Microsoft.CodeAnalysis.VisualBasic.Utilities
 
Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.InlineTemporary
    <ExportCodeRefactoringProvider(LanguageNames.VisualBasic, Name:=PredefinedCodeRefactoringProviderNames.InlineTemporary), [Shared]>
    Partial Friend Class VisualBasicInlineTemporaryCodeRefactoringProvider
        Inherits AbstractInlineTemporaryCodeRefactoringProvider(Of IdentifierNameSyntax, ModifiedIdentifierSyntax)
 
        <ImportingConstructor>
        <SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification:="Used in test code: https://github.com/dotnet/roslyn/issues/42814")>
        Public Sub New()
        End Sub
 
        Public Overloads Overrides Async Function ComputeRefactoringsAsync(context As CodeRefactoringContext) As Task
            Dim document = context.Document
            Dim cancellationToken = context.CancellationToken
 
            If document.Project.Solution.WorkspaceKind = WorkspaceKind.MiscellaneousFiles Then
                Return
            End If
 
            Dim modifiedIdentifier = Await context.TryGetRelevantNodeAsync(Of ModifiedIdentifierSyntax)().ConfigureAwait(False)
 
            If Not modifiedIdentifier.IsParentKind(SyntaxKind.VariableDeclarator) OrElse
               Not modifiedIdentifier.Parent.IsParentKind(SyntaxKind.LocalDeclarationStatement) Then
 
                Return
            End If
 
            Dim variableDeclarator = DirectCast(modifiedIdentifier.Parent, VariableDeclaratorSyntax)
            Dim localDeclarationStatement = DirectCast(variableDeclarator.Parent, LocalDeclarationStatementSyntax)
 
            If Not variableDeclarator.HasInitializer() Then
                Return
            End If
 
            If localDeclarationStatement.ParentingNodeContainsDiagnostics() Then
                Return
            End If
 
            Dim references = Await GetReferenceLocationsAsync(document, modifiedIdentifier, cancellationToken).ConfigureAwait(False)
            If Not references.Any() Then
                Return
            End If
 
            context.RegisterRefactoring(
                CodeAction.Create(
                    FeaturesResources.Inline_temporary_variable,
                    Function(c) InlineTemporaryAsync(document, modifiedIdentifier, c),
                    NameOf(FeaturesResources.Inline_temporary_variable)),
                variableDeclarator.Span)
        End Function
 
        Private Shared Function HasConflict(
            identifier As IdentifierNameSyntax,
            definition As ModifiedIdentifierSyntax,
            expressionToInline As ExpressionSyntax,
            semanticModel As SemanticModel
        ) As Boolean
 
            If identifier.SpanStart < definition.SpanStart Then
                Return True
            End If
 
            Dim identifierNode = identifier _
                .Ancestors() _
                .TakeWhile(Function(n)
                               Return n.Kind = SyntaxKind.ParenthesizedExpression OrElse
                                      TypeOf n Is CastExpressionSyntax OrElse
                                      TypeOf n Is PredefinedCastExpressionSyntax
                           End Function) _
                .LastOrDefault()
 
            If identifierNode Is Nothing Then
                identifierNode = identifier
            End If
 
            If TypeOf identifierNode.Parent Is AssignmentStatementSyntax Then
                Dim assignment = CType(identifierNode.Parent, AssignmentStatementSyntax)
                If assignment.Left Is identifierNode Then
                    Return True
                End If
            End If
 
            If TypeOf identifierNode.Parent Is ArgumentSyntax Then
                If TypeOf expressionToInline Is LiteralExpressionSyntax OrElse
                   TypeOf expressionToInline Is CastExpressionSyntax OrElse
                   TypeOf expressionToInline Is PredefinedCastExpressionSyntax Then
 
                    Dim argument = DirectCast(identifierNode.Parent, ArgumentSyntax)
                    Dim parameter = argument.DetermineParameter(semanticModel)
                    If parameter IsNot Nothing Then
                        Return parameter.RefKind <> RefKind.None
                    End If
                End If
            End If
 
            Return False
        End Function
 
        Private Shared ReadOnly s_definitionAnnotation As New SyntaxAnnotation
        Private Shared ReadOnly s_referenceAnnotation As New SyntaxAnnotation
        Private Shared ReadOnly s_initializerAnnotation As New SyntaxAnnotation
        Private Shared ReadOnly s_expressionToInlineAnnotation As New SyntaxAnnotation
 
        Private Shared Async Function InlineTemporaryAsync(document As Document, modifiedIdentifier As ModifiedIdentifierSyntax, cancellationToken As CancellationToken) As Task(Of Document)
            ' First, annotate the modified identifier so that we can get back to it later.
            Dim updatedDocument = Await document.ReplaceNodeAsync(modifiedIdentifier, modifiedIdentifier.WithAdditionalAnnotations(s_definitionAnnotation), cancellationToken).ConfigureAwait(False)
            Dim semanticModel = Await updatedDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False)
 
            modifiedIdentifier = Await FindDefinitionAsync(updatedDocument, cancellationToken).ConfigureAwait(False)
 
            ' Create the expression that we're actually going to inline
            Dim expressionToInline = Await CreateExpressionToInlineAsync(updatedDocument, cancellationToken).ConfigureAwait(False)
 
            ' Collect the identifier names for each reference.
            Dim references = Await GetReferenceLocationsAsync(updatedDocument, modifiedIdentifier, cancellationToken).ConfigureAwait(False)
            Dim syntaxRoot = Await updatedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False)
 
            ' Collect the target statement for each reference.
 
            Dim nonConflictingIdentifierNodes = references _
                .Where(Function(ident) Not HasConflict(ident, modifiedIdentifier, expressionToInline, semanticModel))
 
            ' Add referenceAnnotations to identifier nodes being replaced.
            updatedDocument = Await updatedDocument.ReplaceNodesAsync(
                nonConflictingIdentifierNodes,
                Function(o, n) n.WithAdditionalAnnotations(s_referenceAnnotation),
                cancellationToken).ConfigureAwait(False)
 
            semanticModel = Await updatedDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False)
            modifiedIdentifier = Await FindDefinitionAsync(updatedDocument, cancellationToken).ConfigureAwait(False)
 
            ' Get the annotated reference nodes.
            nonConflictingIdentifierNodes = Await FindReferenceAnnotatedNodesAsync(updatedDocument, cancellationToken).ConfigureAwait(False)
 
            Dim topMostStatements = nonConflictingIdentifierNodes _
                .Select(Function(ident) GetTopMostStatementForExpression(ident))
 
            ' Next, get the top-most statement of the local declaration
            Dim variableDeclarator = DirectCast(modifiedIdentifier.Parent, VariableDeclaratorSyntax)
            Dim localDeclaration = DirectCast(variableDeclarator.Parent, LocalDeclarationStatementSyntax)
            Dim originalInitializerSymbolInfo = semanticModel.GetSymbolInfo(variableDeclarator.GetInitializer(), cancellationToken)
 
            Dim topMostStatementOfLocalDeclaration = If(localDeclaration.HasAncestor(Of ExpressionSyntax),
                                                        localDeclaration.Ancestors().OfType(Of ExpressionSyntax).Last().FirstAncestorOrSelf(Of StatementSyntax)(),
                                                        localDeclaration)
 
            topMostStatements = topMostStatements.Concat(topMostStatementOfLocalDeclaration)
 
            ' Next get the statements before and after the top-most statement of the local declaration
            Dim previousStatement = topMostStatementOfLocalDeclaration.GetPreviousStatement()
            If previousStatement IsNot Nothing Then
                topMostStatements = topMostStatements.Concat(previousStatement)
            End If
 
            ' Now, add the statement *after* each top-level statement.
            Dim nextStatements = topMostStatements _
                .Select(Function(stmt) stmt.GetNextStatement()) _
                .WhereNotNull()
 
            topMostStatements = topMostStatements _
                .Concat(nextStatements) _
                .Distinct()
 
            ' Make each target statement semantically explicit.
            updatedDocument = Await updatedDocument.ReplaceNodesAsync(
                topMostStatements,
                Function(o, n)
                    Return Simplifier.Expand(DirectCast(n, StatementSyntax), semanticModel, document.Project.Solution.Services, cancellationToken:=cancellationToken)
                End Function,
                cancellationToken).ConfigureAwait(False)
 
            semanticModel = Await updatedDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False)
            Dim semanticModelBeforeInline = semanticModel
 
            modifiedIdentifier = Await FindDefinitionAsync(updatedDocument, cancellationToken).ConfigureAwait(False)
            Dim scope = GetScope(modifiedIdentifier)
            Dim newScope = ReferenceRewriter.Visit(semanticModel, scope, modifiedIdentifier, expressionToInline, cancellationToken)
 
            updatedDocument = Await updatedDocument.ReplaceNodeAsync(scope, newScope.WithAdditionalAnnotations(Formatter.Annotation), cancellationToken).ConfigureAwait(False)
            semanticModel = Await updatedDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False)
 
            modifiedIdentifier = Await FindDefinitionAsync(updatedDocument, cancellationToken).ConfigureAwait(False)
            newScope = GetScope(modifiedIdentifier)
            Dim conflicts = newScope.GetAnnotatedNodesAndTokens(ConflictAnnotation.Kind)
            Dim declaratorConflicts = modifiedIdentifier.GetAnnotatedNodesAndTokens(ConflictAnnotation.Kind)
 
            ' Note that we only remove the local declaration if there weren't any conflicts,
            ' unless those conflicts are inside the local declaration.
            If conflicts.Count() = declaratorConflicts.Count() Then
                ' Certain semantic conflicts can be detected only after the reference rewriter has inlined the expression
                Dim newDocument = Await DetectSemanticConflictsAsync(updatedDocument,
                                                                semanticModel,
                                                                semanticModelBeforeInline,
                                                                originalInitializerSymbolInfo,
                                                                cancellationToken).ConfigureAwait(False)
                If updatedDocument Is newDocument Then
                    ' No semantic conflicts, we can remove the definition.
                    updatedDocument = Await updatedDocument.ReplaceNodeAsync(newScope, RemoveDefinition(modifiedIdentifier, newScope), cancellationToken).ConfigureAwait(False)
                Else
                    ' There were some semantic conflicts, don't remove the definition.
                    updatedDocument = newDocument
                End If
            End If
 
            Return updatedDocument
        End Function
 
        Private Shared Async Function FindDefinitionAsync(document As Document, cancellationToken As CancellationToken) As Task(Of ModifiedIdentifierSyntax)
            Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False)
            Dim result = root _
                .GetAnnotatedNodesAndTokens(s_definitionAnnotation) _
                .Single() _
                .AsNode()
 
            Return DirectCast(result, ModifiedIdentifierSyntax)
        End Function
 
        Private Shared Async Function FindReferenceAnnotatedNodesAsync(document As Document, cancellationToken As CancellationToken) As Task(Of IEnumerable(Of IdentifierNameSyntax))
            Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False)
            Return FindReferenceAnnotatedNodes(root)
        End Function
 
        Private Shared Iterator Function FindReferenceAnnotatedNodes(root As SyntaxNode) As IEnumerable(Of IdentifierNameSyntax)
            Dim annotatedNodesAndTokens = root.GetAnnotatedNodesAndTokens(s_referenceAnnotation)
 
            For Each nodeOrToken In annotatedNodesAndTokens
                If nodeOrToken.IsKind(SyntaxKind.IdentifierName) Then
                    Yield DirectCast(nodeOrToken.AsNode(), IdentifierNameSyntax)
                End If
            Next
        End Function
 
        Private Shared Function GetScope(modifiedIdentifier As ModifiedIdentifierSyntax) As SyntaxNode
            Dim variableDeclarator = DirectCast(modifiedIdentifier.Parent, VariableDeclaratorSyntax)
            Dim localDeclaration = DirectCast(variableDeclarator.Parent, LocalDeclarationStatementSyntax)
 
            Return localDeclaration.Parent
        End Function
 
        Private Shared Function GetUpdatedDeclaration(modifiedIdentifier As ModifiedIdentifierSyntax) As LocalDeclarationStatementSyntax
            Dim variableDeclarator = DirectCast(modifiedIdentifier.Parent, VariableDeclaratorSyntax)
            Dim localDeclaration = DirectCast(variableDeclarator.Parent, LocalDeclarationStatementSyntax)
 
            If localDeclaration.Declarators.Count > 1 And variableDeclarator.Names.Count = 1 Then
                Return localDeclaration.RemoveNode(variableDeclarator, SyntaxRemoveOptions.KeepEndOfLine)
            End If
 
            If variableDeclarator.Names.Count > 1 Then
                Return localDeclaration.RemoveNode(modifiedIdentifier, SyntaxRemoveOptions.KeepEndOfLine)
            End If
 
            Throw ExceptionUtilities.Unreachable
        End Function
 
        Private Shared Function RemoveDefinition(modifiedIdentifier As ModifiedIdentifierSyntax, newBlock As SyntaxNode) As SyntaxNode
            Dim variableDeclarator = DirectCast(modifiedIdentifier.Parent, VariableDeclaratorSyntax)
            Dim localDeclaration = DirectCast(variableDeclarator.Parent, LocalDeclarationStatementSyntax)
 
            If variableDeclarator.Names.Count > 1 OrElse
               localDeclaration.Declarators.Count > 1 Then
 
                ' In this case, we need to remove the definition from either the declarators or the names of
                ' the local declaration.
                Dim newDeclaration = GetUpdatedDeclaration(modifiedIdentifier) _
                    .WithAdditionalAnnotations(Formatter.Annotation)
 
                Dim newStatements = newBlock.GetExecutableBlockStatements().Replace(localDeclaration, newDeclaration)
                Return newBlock.ReplaceStatements(newStatements)
            Else
                ' In this case, we're removing the local declaration. Care must be taken to move any
                ' non-whitespace trivia to the next statement.
 
                Dim blockStatements = newBlock.GetExecutableBlockStatements()
                Dim declarationIndex = blockStatements.IndexOf(localDeclaration)
 
                Dim leadingTrivia = localDeclaration _
                    .GetLeadingTrivia() _
                    .Reverse() _
                    .SkipWhile(Function(t) t.IsKind(SyntaxKind.WhitespaceTrivia)) _
                    .Reverse()
 
                Dim trailingTrivia = localDeclaration _
                    .GetTrailingTrivia() _
                    .SkipWhile(Function(t) t.IsKind(SyntaxKind.WhitespaceTrivia, SyntaxKind.EndOfLineTrivia, SyntaxKind.ColonTrivia))
 
                Dim newLeadingTrivia = leadingTrivia.Concat(trailingTrivia)
 
                ' Ensure that we leave a line break if our local declaration ended with a comment.
                If newLeadingTrivia.Any() AndAlso newLeadingTrivia.Last().IsKind(SyntaxKind.CommentTrivia) Then
                    newLeadingTrivia = newLeadingTrivia.Concat(SyntaxFactory.CarriageReturnLineFeed)
                End If
 
                Dim nextToken = localDeclaration.GetLastToken().GetNextToken()
                Dim newNextToken = nextToken _
                    .WithPrependedLeadingTrivia(newLeadingTrivia.ToSyntaxTriviaList()) _
                    .WithAdditionalAnnotations(Formatter.Annotation)
 
                Dim previousToken = localDeclaration.GetFirstToken().GetPreviousToken()
 
                ' If the previous token has trailing colon trivia, replace it with a new line.
                Dim previousTokenTrailingTrivia = previousToken.TrailingTrivia.ToList()
                If previousTokenTrailingTrivia.Count > 0 AndAlso previousTokenTrailingTrivia.Last().IsKind(SyntaxKind.ColonTrivia) Then
                    previousTokenTrailingTrivia(previousTokenTrailingTrivia.Count - 1) = SyntaxFactory.CarriageReturnLineFeed
                End If
 
                Dim newPreviousToken = previousToken _
                    .WithTrailingTrivia(previousTokenTrailingTrivia) _
                    .WithAdditionalAnnotations(Formatter.Annotation)
 
                newBlock = newBlock.ReplaceTokens({previousToken, nextToken},
                    Function(oldToken, newToken)
                        If oldToken = nextToken Then
                            Return newNextToken
                        ElseIf oldToken = previousToken Then
                            Return newPreviousToken
                        Else
                            Return newToken
                        End If
                    End Function)
 
                Dim newBlockStatements = newBlock.GetExecutableBlockStatements()
                Dim newStatements = newBlockStatements.RemoveAt(declarationIndex)
 
                Return newBlock.ReplaceStatements(newStatements)
            End If
        End Function
 
        Private Shared Function AddExplicitArgumentListIfNeeded(expression As ExpressionSyntax, semanticModel As SemanticModel) As ExpressionSyntax
            If expression.IsKind(SyntaxKind.IdentifierName) OrElse
               expression.IsKind(SyntaxKind.GenericName) OrElse
               expression.IsKind(SyntaxKind.SimpleMemberAccessExpression) Then
 
                Dim symbol = semanticModel.GetSymbolInfo(expression).Symbol
                If symbol IsNot Nothing AndAlso
                  (symbol.Kind = SymbolKind.Method OrElse symbol.Kind = SymbolKind.Property) Then
 
                    Dim trailingTrivia = expression.GetTrailingTrivia()
 
                    Return SyntaxFactory _
                        .InvocationExpression(
                            expression:=expression.WithTrailingTrivia(CType(Nothing, SyntaxTriviaList)),
                            argumentList:=SyntaxFactory.ArgumentList().WithTrailingTrivia(trailingTrivia)) _
                        .WithAdditionalAnnotations(Simplifier.Annotation)
                End If
            End If
 
            Return expression
        End Function
 
        Private Shared Async Function CreateExpressionToInlineAsync(document As Document, cancellationToken As CancellationToken) As Task(Of ExpressionSyntax)
            ' TODO: We should be using a speculative semantic model in the method rather than forking new semantic model every time.
 
            Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False)
 
            Dim modifiedIdentifier = Await FindDefinitionAsync(document, cancellationToken).ConfigureAwait(False)
            Dim initializer = DirectCast(modifiedIdentifier.Parent, VariableDeclaratorSyntax).GetInitializer()
            Dim newInitializer = AddExplicitArgumentListIfNeeded(initializer, semanticModel) _
                                 .WithAdditionalAnnotations(s_initializerAnnotation)
 
            Dim updatedDocument = Await document.ReplaceNodeAsync(initializer, newInitializer, cancellationToken).ConfigureAwait(False)
 
            modifiedIdentifier = Await FindDefinitionAsync(updatedDocument, cancellationToken).ConfigureAwait(False)
            initializer = DirectCast(modifiedIdentifier.Parent, VariableDeclaratorSyntax).GetInitializer()
 
            Dim explicitInitializer = Await Simplifier.ExpandAsync(initializer, updatedDocument, cancellationToken:=cancellationToken).ConfigureAwait(False)
 
            Dim lastToken = explicitInitializer.GetLastToken()
            explicitInitializer = explicitInitializer.ReplaceToken(lastToken, lastToken.WithTrailingTrivia(SyntaxTriviaList.Empty))
 
            updatedDocument = Await updatedDocument.ReplaceNodeAsync(initializer, explicitInitializer, cancellationToken).ConfigureAwait(False)
            semanticModel = Await updatedDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False)
 
            modifiedIdentifier = Await FindDefinitionAsync(updatedDocument, cancellationToken).ConfigureAwait(False)
            explicitInitializer = DirectCast(modifiedIdentifier.Parent, VariableDeclaratorSyntax).GetInitializer()
 
            Dim local = DirectCast(semanticModel.GetDeclaredSymbol(modifiedIdentifier, cancellationToken), ILocalSymbol)
            Dim wasCastAdded As Boolean = False
            explicitInitializer = explicitInitializer.CastIfPossible(local.Type,
                                                                     modifiedIdentifier.SpanStart,
                                                                     semanticModel,
                                                                     wasCastAdded,
                                                                     cancellationToken)
 
            Return explicitInitializer.WithAdditionalAnnotations(s_expressionToInlineAnnotation)
        End Function
 
        Private Shared Function GetTopMostStatementForExpression(expression As ExpressionSyntax) As StatementSyntax
            Return expression.AncestorsAndSelf().OfType(Of ExpressionSyntax).Last().FirstAncestorOrSelf(Of StatementSyntax)()
        End Function
 
        Private Shared Async Function DetectSemanticConflictsAsync(
            inlinedDocument As Document,
            newSemanticModelForInlinedDocument As SemanticModel,
            semanticModelBeforeInline As SemanticModel,
            originalInitializerSymbolInfo As SymbolInfo,
            cancellationToken As CancellationToken
        ) As Task(Of Document)
 
            ' In this method we detect if inlining the expression introduced the following semantic change:
            '  The symbol info associated with any of the inlined expressions does not match the symbol info for original initializer expression prior to inline.
 
            ' If any semantic changes were introduced by inlining, we update the document with conflict annotations.
            ' Otherwise we return the given inlined document without any changes.
 
            Dim syntaxRootBeforeInline = Await semanticModelBeforeInline.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(False)
            ' Get all the identifier nodes which were replaced with inlined expression.
            Dim originalIdentifierNodes = FindReferenceAnnotatedNodes(syntaxRootBeforeInline).ToArray()
 
            If originalIdentifierNodes.IsEmpty Then
                ' No conflicts
                Return inlinedDocument
            End If
 
            ' Get all the inlined expression nodes.
            Dim syntaxRootAfterInline = Await inlinedDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False)
            Dim inlinedExprNodes = syntaxRootAfterInline.GetAnnotatedNodesAndTokens(s_expressionToInlineAnnotation).ToArray()
            Debug.Assert(originalIdentifierNodes.Length = inlinedExprNodes.Length)
 
            Dim replacementNodesWithChangedSemantics As Dictionary(Of SyntaxNode, SyntaxNode) = Nothing
 
            For i = 0 To originalIdentifierNodes.Length - 1
                Dim originalNode = originalIdentifierNodes(i)
                Dim inlinedNode = DirectCast(inlinedExprNodes(i).AsNode(), ExpressionSyntax)
 
                ' inlinedNode is the expanded form of the actual initializer expression in the original document.
                ' We have annotated the inner initializer with a special syntax annotation "_initializerAnnotation".
                ' Get this annotated node and compute the symbol info for this node in the inlined document.
                Dim innerInitializerInInlineNode = DirectCast(inlinedNode.GetAnnotatedNodesAndTokens(s_initializerAnnotation).Single().AsNode, ExpressionSyntax)
                Dim newInitializerSymbolInfo = newSemanticModelForInlinedDocument.GetSymbolInfo(innerInitializerInInlineNode, cancellationToken)
 
                ' Verification: The symbol info associated with any of the inlined expressions does not match the symbol info for original initializer expression prior to inline.
                If Not SpeculationAnalyzer.SymbolInfosAreCompatible(originalInitializerSymbolInfo, newInitializerSymbolInfo, performEquivalenceCheck:=True) Then
                    If replacementNodesWithChangedSemantics Is Nothing Then
                        replacementNodesWithChangedSemantics = New Dictionary(Of SyntaxNode, SyntaxNode)
                    End If
 
                    replacementNodesWithChangedSemantics.Add(inlinedNode, originalNode)
                End If
            Next
 
            If replacementNodesWithChangedSemantics Is Nothing Then
                ' No conflicts.
                Return inlinedDocument
            End If
 
            ' Replace the conflicting inlined nodes with the original nodes annotated with conflict annotation.
            Dim conflictAnnotationAdder = Function(oldNode As SyntaxNode, newNode As SyntaxNode) As SyntaxNode
                                              Return newNode _
                                                  .WithAdditionalAnnotations(ConflictAnnotation.Create(FeaturesResources.Conflict_s_detected))
                                          End Function
 
            Return Await inlinedDocument.ReplaceNodesAsync(replacementNodesWithChangedSemantics.Keys, conflictAnnotationAdder, cancellationToken).ConfigureAwait(False)
        End Function
    End Class
End Namespace