File: src\Analyzers\VisualBasic\CodeFixes\AddExplicitCast\VisualBasicAddExplicitCastCodeFixProvider.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.Collections.Immutable
Imports System.Composition
Imports System.Diagnostics.CodeAnalysis
Imports System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.CodeFixes.AddExplicitCast
Imports Microsoft.CodeAnalysis.LanguageService
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
 
Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.AddExplicitCast
    <ExportCodeFixProvider(LanguageNames.VisualBasic, Name:=PredefinedCodeFixProviderNames.AddExplicitCast), [Shared]>
    Partial Friend NotInheritable Class VisualBasicAddExplicitCastCodeFixProvider
        Inherits AbstractAddExplicitCastCodeFixProvider(Of ExpressionSyntax)
 
        Friend Const BC30512 As String = "BC30512" ' Option Strict On disallows implicit conversions from '{0}' to '{1}'.
        Friend Const BC42016 As String = "BC42016" ' Implicit conversions from '{0}' to '{1}'.
        Friend Const BC30518 As String = "BC30518" ' Overload resolution failed because no accessible 'sub1' can be called with these arguments.
        Friend Const BC30519 As String = "BC30519" ' Overload resolution failed because no accessible 'sub1' can be called without a narrowing conversion.
 
        Private ReadOnly _fixer As ArgumentFixer
 
        <ImportingConstructor>
        <SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification:="Used in test code: https://github.com/dotnet/roslyn/issues/42814")>
        Public Sub New()
            _fixer = New ArgumentFixer()
        End Sub
 
        Public Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(BC30512, BC42016, BC30518, BC30519)
 
        Protected Overrides Sub GetPartsOfCastOrConversionExpression(
                expression As ExpressionSyntax,
                ByRef type As SyntaxNode,
                ByRef castedExpression As ExpressionSyntax)
            Dim directCastExpression = TryCast(expression, DirectCastExpressionSyntax)
            If directCastExpression IsNot Nothing Then
                type = directCastExpression.Type
                castedExpression = directCastExpression.Expression
                Return
            End If
 
            Dim conversionExpression = DirectCast(expression, CTypeExpressionSyntax)
            type = conversionExpression.Type
            castedExpression = conversionExpression.Expression
        End Sub
 
        Protected Overrides Function Cast(expression As ExpressionSyntax, type As ITypeSymbol) As ExpressionSyntax
            Return expression.Cast(type, isResultPredefinedCast:=Nothing)
        End Function
 
        Protected Overrides Sub AddPotentialTargetTypes(
                document As Document,
                semanticModel As SemanticModel,
                root As SyntaxNode,
                diagnosticId As String,
                spanNode As ExpressionSyntax,
                candidates As ArrayBuilder(Of (node As ExpressionSyntax, type As ITypeSymbol)),
                cancellationToken As CancellationToken)
 
            Select Case diagnosticId
                Case BC30512, BC42016
                    Dim argument = spanNode.GetAncestors(Of ArgumentSyntax).FirstOrDefault()
                    If argument IsNot Nothing AndAlso argument.GetExpression.Equals(spanNode) Then
                        ' spanNode is an argument expression
                        Dim argumentList = DirectCast(argument.Parent, ArgumentListSyntax)
                        Dim invocationNode = argumentList.Parent
 
                        candidates.AddRange(_fixer.GetPotentialConversionTypes(
                            document, semanticModel, root, argument, argumentList, invocationNode, cancellationToken))
                    Else
                        ' spanNode is a right expression in assignment operation
                        Dim inferenceService = document.GetRequiredLanguageService(Of ITypeInferenceService)()
                        Dim conversionType = inferenceService.InferType(semanticModel, spanNode, objectAsDefault:=False,
                            cancellationToken)
                        candidates.Add((spanNode, conversionType))
                    End If
                Case BC30518, BC30519
                    Dim invocationExpressionNode = spanNode.GetAncestors(Of InvocationExpressionSyntax).FirstOrDefault(
                        Function(node) node.ArgumentList IsNot Nothing)
 
                    Dim attributeNode = spanNode.GetAncestors(Of AttributeSyntax).FirstOrDefault(
                        Function(node) node.ArgumentList IsNot Nothing)
 
                    ' Collect available cast pairs without target argument
                    If invocationExpressionNode IsNot Nothing Then
                        candidates.AddRange(
                            GetPotentialConversionTypesWithInvocationNode(document, semanticModel, root, invocationExpressionNode, cancellationToken))
                    ElseIf attributeNode IsNot Nothing Then
                        candidates.AddRange(
                            GetPotentialConversionTypesWithInvocationNode(document, semanticModel, root, attributeNode, cancellationToken))
                    End If
            End Select
        End Sub
 
        ''' <summary>
        ''' Find the first argument that need to be cast
        ''' </summary>
        ''' <param name="parameters"> The parameters of method</param>
        ''' <param name="arguments"> The arguments of invocation node</param>
        ''' <returns>
        ''' Return the first argument that need to be cast, could be null if such argument doesn't exist
        ''' </returns>
        Private Shared Function GetTargetArgument(
                document As Document,
                semanticModel As SemanticModel,
                parameters As ImmutableArray(Of IParameterSymbol),
                arguments As SeparatedSyntaxList(Of ArgumentSyntax)) As ArgumentSyntax
            If parameters.Length = 0 Then
                Return Nothing
            End If
 
            Dim syntaxFacts = document.GetRequiredLanguageService(Of ISyntaxFactsService)()
 
            For i = 0 To arguments.Count - 1
                ' Parameter index cannot out of its range, #arguments Is larger than #parameter only if 
                ' the last parameter with keyword params
                Dim parameterIndex = Math.Min(i, parameters.Length - 1)
 
                ' If the argument has a name, get the corresponding parameter index
                Dim argumentName = syntaxFacts.GetNameForArgument(arguments(i))
                If argumentName IsNot String.Empty AndAlso Not FindCorrespondingParameterByName(argumentName, parameters,
                        parameterIndex) Then
                    Return Nothing
                End If
 
                Dim argumentExpression = arguments(i).GetExpression
                If argumentExpression Is Nothing Then
                    Continue For
                End If
 
                Dim parameterType = parameters(parameterIndex).Type
 
                If parameters(parameterIndex).IsParams Then
                    Dim paramsType = TryCast(parameterType, IArrayTypeSymbol)
                    If paramsType IsNot Nothing Then
                        Dim conversion = semanticModel.ClassifyConversion(argumentExpression, paramsType.ElementType)
                        If conversion.Exists AndAlso Not conversion.IsIdentity Then
                            Return arguments(i)
                        End If
                    End If
                End If
 
                Dim argumentConversion = semanticModel.ClassifyConversion(argumentExpression, parameterType)
                If argumentConversion.Exists AndAlso Not argumentConversion.IsIdentity Then
                    Return arguments(i)
                End If
            Next
 
            Return Nothing
        End Function
 
        ''' <summary>
        ''' Collect available cast pairs without target argument.
        ''' For each method, the first argument need to be cast is the target argument.
        ''' Return format is (argument expression, potential conversion type).
        ''' </summary>
        ''' <param name="invocationNode">The invocation node that contains some arguments need to be cast</param>
        ''' <returns>
        ''' Return all the available cast pairs, format is (argument expression, potential conversion type)
        ''' </returns>
        Private Function GetPotentialConversionTypesWithInvocationNode(
                document As Document,
                semanticModel As SemanticModel,
                root As SyntaxNode,
                invocationNode As SyntaxNode,
                cancellationToken As CancellationToken) As ImmutableArray(Of (ExpressionSyntax, ITypeSymbol))
            Dim argumentList = invocationNode.ChildNodes.OfType(Of ArgumentListSyntax).FirstOrDefault()
 
            Dim symbolInfo = semanticModel.GetSymbolInfo(invocationNode, cancellationToken)
            Dim candidateSymbols = symbolInfo.CandidateSymbols
 
            Dim mutablePotentialConversionTypes = ArrayBuilder(Of (ExpressionSyntax, ITypeSymbol)).GetInstance()
 
            For Each candidateSymbol In candidateSymbols.OfType(Of IMethodSymbol)()
                Dim targetArgument = GetTargetArgument(document, semanticModel, candidateSymbol.Parameters, argumentList.Arguments)
                If targetArgument Is Nothing Then
                    Continue For
                End If
 
                Dim conversionType As ITypeSymbol = Nothing
                If _fixer.CanArgumentTypesBeConvertedToParameterTypes(
                        document, semanticModel, root, argumentList, candidateSymbol.Parameters, targetArgument, cancellationToken, conversionType) Then
                    mutablePotentialConversionTypes.Add((targetArgument.GetExpression(), conversionType))
                End If
            Next
 
            ' Sort the potential conversion types by inheritance distance, so that
            ' operations are in order And user can choose least specific types(more accurate)
            mutablePotentialConversionTypes.Sort(New InheritanceDistanceComparer(Of ExpressionSyntax)(semanticModel))
 
            Return mutablePotentialConversionTypes.ToImmutable()
        End Function
    End Class
End Namespace