File: Simplification\Simplifiers\ExpressionSimplifier.vb
Web Access
Project: src\src\Workspaces\VisualBasic\Portable\Microsoft.CodeAnalysis.VisualBasic.Workspaces.vbproj (Microsoft.CodeAnalysis.VisualBasic.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.
 
Imports System.Runtime.InteropServices
Imports System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.CodeStyle
Imports Microsoft.CodeAnalysis.Options
Imports Microsoft.CodeAnalysis.Simplification
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Microsoft.CodeAnalysis.VisualBasic.Utilities
 
Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers
    Friend Class ExpressionSimplifier
        Inherits AbstractVisualBasicSimplifier(Of ExpressionSyntax, ExpressionSyntax)
 
        Public Shared ReadOnly Instance As New ExpressionSimplifier()
 
        Private Sub New()
        End Sub
 
        Public Overrides Function TrySimplify(expression As ExpressionSyntax,
                                              semanticModel As SemanticModel,
                                              options As VisualBasicSimplifierOptions,
                                              ByRef replacementNode As ExpressionSyntax,
                                              ByRef issueSpan As TextSpan,
                                              cancellationToken As CancellationToken) As Boolean
 
            Dim memberAccessExpression = TryCast(expression, MemberAccessExpressionSyntax)
            If memberAccessExpression?.Expression?.Kind() = SyntaxKind.MeExpression Then
                If Not MemberAccessExpressionSimplifier.Instance.ShouldSimplifyThisMemberAccessExpression(
                    memberAccessExpression, semanticModel, options, thisExpression:=Nothing, notificationOption:=Nothing, cancellationToken) Then
                    Return False
                End If
 
                replacementNode = memberAccessExpression.GetNameWithTriviaMoved()
                issueSpan = memberAccessExpression.Expression.Span
                Return True
            End If
 
            If TryReduceExplicitName(expression, semanticModel, replacementNode, issueSpan, options, cancellationToken) Then
                Return True
            End If
 
            Return TrySimplify(expression, semanticModel, replacementNode, issueSpan)
        End Function
 
        Private Shared Function TryReduceExplicitName(
            expression As ExpressionSyntax,
            semanticModel As SemanticModel,
            <Out> ByRef replacementNode As ExpressionSyntax,
            <Out> ByRef issueSpan As TextSpan,
            options As VisualBasicSimplifierOptions,
            cancellationToken As CancellationToken
        ) As Boolean
            replacementNode = Nothing
            issueSpan = Nothing
 
            If expression.Kind = SyntaxKind.SimpleMemberAccessExpression Then
                Dim memberAccess = DirectCast(expression, MemberAccessExpressionSyntax)
                Return TryReduce(
                    memberAccess, semanticModel, replacementNode, issueSpan, options, cancellationToken)
            ElseIf TypeOf expression Is NameSyntax Then
                Dim name = DirectCast(expression, NameSyntax)
                Return NameSimplifier.Instance.TrySimplify(
                    name, semanticModel, options, replacementNode, issueSpan, cancellationToken)
            End If
 
            Return False
        End Function
 
        Private Shared Function TryReduce(
            memberAccess As MemberAccessExpressionSyntax,
            semanticModel As SemanticModel,
            <Out> ByRef replacementNode As ExpressionSyntax,
            <Out> ByRef issueSpan As TextSpan,
            options As VisualBasicSimplifierOptions,
            cancellationToken As CancellationToken
        ) As Boolean
            If memberAccess.Expression Is Nothing OrElse memberAccess.Name Is Nothing Then
                Return False
            End If
 
            If memberAccess.HasAnnotations(SpecialTypeAnnotation.Kind) Then
                replacementNode = SyntaxFactory.PredefinedType(
                    SyntaxFactory.Token(
                        GetPredefinedKeywordKind(SpecialTypeAnnotation.GetSpecialType(memberAccess.GetAnnotations(SpecialTypeAnnotation.Kind).First())))) _
                            .WithLeadingTrivia(memberAccess.GetLeadingTrivia())
 
                issueSpan = memberAccess.Span
                Return True
            End If
 
            ' See https//github.com/dotnet/roslyn/issues/40974
            '
            ' To be very safe, we only support simplifying code that bound to a symbol without any
            ' sort of problems.  We could potentially relax this in the future.  However, we would
            ' need to be very careful about the implications of us offering to fixup 'broken' code 
            ' in a manner that might end up making things worse Or confusing the user.
            Dim symbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, memberAccess)
            If symbol Is Nothing Then
                Return False
            End If
 
            If Not memberAccess.IsRightSideOfDot() Then
                Dim aliasReplacement As IAliasSymbol = Nothing
 
                If TryReplaceWithAlias(memberAccess, semanticModel, aliasReplacement) Then
                    Dim identifierToken = SyntaxFactory.Identifier(
                                memberAccess.GetLeadingTrivia(),
                                aliasReplacement.Name,
                                memberAccess.GetTrailingTrivia())
 
                    identifierToken = TryEscapeIdentifierToken(identifierToken)
                    replacementNode = SyntaxFactory.IdentifierName(identifierToken)
 
                    issueSpan = memberAccess.Span
 
                    ' In case the alias name is the same as the last name of the alias target, we only include 
                    ' the left part of the name in the unnecessary span to Not confuse uses.
                    If memberAccess.Name.Identifier.ValueText = identifierToken.ValueText Then
                        issueSpan = memberAccess.Expression.Span
                    End If
 
                    Return True
                End If
 
                If PreferPredefinedTypeKeywordInMemberAccess(memberAccess, options) Then
                    If (symbol IsNot Nothing AndAlso symbol.IsKind(SymbolKind.NamedType)) Then
                        Dim keywordKind = GetPredefinedKeywordKind(DirectCast(symbol, INamedTypeSymbol).SpecialType)
                        If keywordKind <> SyntaxKind.None Then
                            replacementNode = SyntaxFactory.PredefinedType(
                                                SyntaxFactory.Token(
                                                    memberAccess.GetLeadingTrivia(),
                                                    keywordKind,
                                                    memberAccess.GetTrailingTrivia()))
 
                            replacementNode = replacementNode.WithAdditionalAnnotations(
                                    New SyntaxAnnotation(NameOf(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess)))
 
                            issueSpan = memberAccess.Span
                            Return True
                        End If
                    End If
                End If
            End If
 
            ' a module name was inserted by the name expansion, so removing this should be tried first.
            If memberAccess.HasAnnotation(SimplificationHelpers.SimplifyModuleNameAnnotation) Then
                If TryOmitModuleName(memberAccess, semanticModel, symbol, replacementNode, issueSpan, cancellationToken) Then
                    Return True
                End If
            End If
 
            replacementNode = memberAccess.GetNameWithTriviaMoved()
            issueSpan = memberAccess.Expression.Span
 
            If CanReplaceWithReducedName(memberAccess, replacementNode, semanticModel, symbol, cancellationToken) Then
                Return True
            End If
 
            If TryOmitModuleName(memberAccess, semanticModel, symbol, replacementNode, issueSpan, cancellationToken) Then
                Return True
            End If
 
            Return False
        End Function
 
        Private Overloads Shared Function TrySimplify(
            expression As ExpressionSyntax,
            semanticModel As SemanticModel,
            <Out> ByRef replacementNode As ExpressionSyntax,
            <Out> ByRef issueSpan As TextSpan
        ) As Boolean
            replacementNode = Nothing
            issueSpan = Nothing
 
            Select Case expression.Kind
                Case SyntaxKind.SimpleMemberAccessExpression
                    If True Then
                        Dim memberAccess = DirectCast(expression, MemberAccessExpressionSyntax)
                        Dim newLeft As ExpressionSyntax = Nothing
                        If TrySimplifyMemberAccessOrQualifiedName(memberAccess.Expression, memberAccess.Name, semanticModel, newLeft, issueSpan) Then
                            ' replacement node might not be in it's simplest form, so add simplify annotation to it.
                            replacementNode = memberAccess.Update(memberAccess.Kind, newLeft, memberAccess.OperatorToken, memberAccess.Name).WithAdditionalAnnotations(Simplifier.Annotation)
 
                            ' Ensure that replacement doesn't change semantics.
                            Return Not ReplacementChangesSemantics(memberAccess, replacementNode, semanticModel)
                        End If
 
                        Return False
                    End If
 
                Case SyntaxKind.QualifiedName
                    If True Then
                        Dim qualifiedName = DirectCast(expression, QualifiedNameSyntax)
                        Dim newLeft As ExpressionSyntax = Nothing
                        If TrySimplifyMemberAccessOrQualifiedName(qualifiedName.Left, qualifiedName.Right, semanticModel, newLeft, issueSpan) Then
                            If Not TypeOf newLeft Is NameSyntax Then
                                Contract.Fail("QualifiedName Left = " + qualifiedName.Left.ToString() + " and QualifiedName Right = " + qualifiedName.Right.ToString() + " . Left is tried to be replaced with the PredefinedType " + replacementNode.ToString())
                            End If
 
                            ' replacement node might not be in it's simplest form, so add simplify annotation to it.
                            replacementNode = qualifiedName.Update(DirectCast(newLeft, NameSyntax), qualifiedName.DotToken, qualifiedName.Right).WithAdditionalAnnotations(Simplifier.Annotation)
 
                            ' Ensure that replacement doesn't change semantics.
                            Return Not ReplacementChangesSemantics(qualifiedName, replacementNode, semanticModel)
                        End If
 
                        Return False
                    End If
            End Select
 
            Return False
        End Function
 
        Private Shared Function ReplacementChangesSemantics(originalExpression As ExpressionSyntax, replacedExpression As ExpressionSyntax, semanticModel As SemanticModel) As Boolean
            Dim speculationAnalyzer = New SpeculationAnalyzer(originalExpression, replacedExpression, semanticModel, CancellationToken.None)
            Return speculationAnalyzer.ReplacementChangesSemantics()
        End Function
 
        ' Note: The caller needs to verify that replacement doesn't change semantics of the original expression.
        Private Shared Function TrySimplifyMemberAccessOrQualifiedName(
            left As ExpressionSyntax,
            right As ExpressionSyntax,
            semanticModel As SemanticModel,
            <Out()> ByRef replacementNode As ExpressionSyntax,
            <Out()> ByRef issueSpan As TextSpan
        ) As Boolean
            replacementNode = Nothing
            issueSpan = Nothing
 
            If left IsNot Nothing AndAlso right IsNot Nothing Then
                Dim leftSymbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, left)
                If leftSymbol IsNot Nothing AndAlso leftSymbol.Kind = SymbolKind.NamedType Then
                    Dim rightSymbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, right)
                    If rightSymbol IsNot Nothing AndAlso (rightSymbol.IsStatic OrElse rightSymbol.Kind = SymbolKind.NamedType) Then
                        ' Static member access or nested type member access.
                        Dim containingType As INamedTypeSymbol = rightSymbol.ContainingType
                        Dim isInCref = left.Ancestors(ascendOutOfTrivia:=True).OfType(Of CrefReferenceSyntax)().Any()
 
                        ' Crefs in VB , irrespective of the expression are parsed as QualifiedName (no MemberAccessExpression)
                        ' Hence the Left can never be a PredefinedType (or anything other than NameSyntax)
                        If isInCref AndAlso TypeOf rightSymbol Is IMethodSymbol AndAlso Not containingType.SpecialType = SpecialType.None Then
                            Return False
                        End If
 
                        If containingType IsNot Nothing AndAlso Not containingType.Equals(leftSymbol) Then
 
                            Dim namedType = TryCast(leftSymbol, INamedTypeSymbol)
                            If namedType IsNot Nothing AndAlso
                               containingType.TypeArguments.Length <> 0 Then
                                Return False
                            End If
 
                            ' We have a static member access or a nested type member access using a more derived type.
                            ' Simplify syntax so as to use accessed member's most immediate containing type instead of the derived type.
                            replacementNode = containingType.GenerateTypeSyntax().WithLeadingTrivia(left.GetLeadingTrivia()).WithTrailingTrivia(left.GetTrailingTrivia()).WithAdditionalAnnotations(Simplifier.Annotation)
                            issueSpan = left.Span
                            Return True
                        End If
                    End If
                End If
            End If
 
            Return False
        End Function
 
        Private Shared Function TryOmitModuleName(memberAccess As MemberAccessExpressionSyntax,
                                           semanticModel As SemanticModel,
                                           symbol As ISymbol,
                                           <Out> ByRef replacementNode As ExpressionSyntax,
                                           <Out> ByRef issueSpan As TextSpan,
                                           cancellationToken As CancellationToken) As Boolean
            If memberAccess.IsParentKind(SyntaxKind.SimpleMemberAccessExpression) Then
                Dim symbolForMemberAccess = semanticModel.GetSymbolInfo(DirectCast(memberAccess.Parent, MemberAccessExpressionSyntax), cancellationToken).Symbol
                If symbolForMemberAccess.IsModuleMember Then
                    replacementNode = memberAccess.Expression.WithLeadingTrivia(memberAccess.GetLeadingTrivia())
                    issueSpan = memberAccess.Name.Span
 
                    Dim parent = DirectCast(memberAccess.Parent, MemberAccessExpressionSyntax)
                    Dim parentReplacement = parent.ReplaceNode(parent.Expression, replacementNode)
 
                    If CanReplaceWithReducedName(parent, parentReplacement, semanticModel, symbol, cancellationToken) Then
                        Return True
                    End If
                End If
            End If
 
            Return False
        End Function
 
        Private Shared Function CanReplaceWithReducedName(
                memberAccess As MemberAccessExpressionSyntax,
                reducedNode As ExpressionSyntax,
                semanticModel As SemanticModel,
                symbol As ISymbol,
                cancellationToken As CancellationToken) As Boolean
            If Not SimplificationHelpers.IsNamespaceOrTypeOrThisParameter(memberAccess.Expression, semanticModel) Then
                Return False
            End If
 
            ' See if we can simplify a member access expression of the form E.M or E.M() to M or M()
            Dim speculationAnalyzer = New SpeculationAnalyzer(memberAccess, reducedNode, semanticModel, cancellationToken)
            If Not speculationAnalyzer.SymbolsForOriginalAndReplacedNodesAreCompatible() OrElse
                speculationAnalyzer.ReplacementChangesSemantics() Then
                Return False
            End If
 
            If memberAccess.Expression.IsKind(SyntaxKind.MyBaseExpression) Then
                Dim enclosingNamedType = semanticModel.GetEnclosingNamedType(memberAccess.SpanStart, cancellationToken)
                If enclosingNamedType IsNot Nothing AndAlso
                    Not enclosingNamedType.IsSealed AndAlso
                    symbol IsNot Nothing AndAlso
                    symbol.IsOverridable() Then
                    Return False
                End If
            End If
 
            Return True
        End Function
    End Class
End Namespace