|
' 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.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Microsoft.CodeAnalysis.VisualBasic.Utilities
Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions
Friend NotInheritable Class CastAnalyzer
Private ReadOnly _castNode As ExpressionSyntax
Private ReadOnly _castExpressionNode As ExpressionSyntax
Private ReadOnly _semanticModel As SemanticModel
Private ReadOnly _assumeCallKeyword As Boolean
Private ReadOnly _cancellationToken As CancellationToken
Private Sub New(
castNode As ExpressionSyntax,
castExpressionNode As ExpressionSyntax,
semanticModel As SemanticModel,
assumeCallKeyword As Boolean,
cancellationToken As CancellationToken)
_castNode = castNode
_castExpressionNode = castExpressionNode
_semanticModel = semanticModel
_assumeCallKeyword = assumeCallKeyword
_cancellationToken = cancellationToken
End Sub
Private Function CastPassedToParamArrayDefinitelyCantBeRemoved(castType As ITypeSymbol) As Boolean
If _castExpressionNode.WalkDownParentheses().IsKind(SyntaxKind.NothingLiteralExpression) Then
Dim argument = TryCast(_castNode.WalkUpParentheses().Parent, ArgumentSyntax)
If argument IsNot Nothing Then
Dim parameter = argument.DetermineParameter(_semanticModel, cancellationToken:=_cancellationToken)
If parameter?.IsParams = True AndAlso TypeOf parameter.Type Is IArrayTypeSymbol Then
Dim parameterType = DirectCast(parameter.Type, IArrayTypeSymbol)
Dim conversion = _semanticModel.Compilation.ClassifyConversion(castType, parameterType)
If conversion.Exists AndAlso conversion.IsWidening Then
Return False
End If
Dim conversionElementType = _semanticModel.Compilation.ClassifyConversion(castType, parameterType.ElementType)
If conversionElementType.Exists AndAlso (conversionElementType.IsIdentity OrElse conversionElementType.IsWidening) Then
Return True
End If
End If
End If
End If
Return False
End Function
Private Shared Function GetOuterCastType(expression As ExpressionSyntax, expressionTypeInfo As TypeInfo, semanticModel As SemanticModel, cancellationToken As CancellationToken) As ITypeSymbol
expression = expression.WalkUpParentheses()
Dim parent = expression.Parent
Dim parentExpression = TryCast(parent, ExpressionSyntax)
If parentExpression IsNot Nothing Then
If TypeOf parentExpression Is CastExpressionSyntax OrElse
TypeOf parentExpression Is PredefinedCastExpressionSyntax Then
Return semanticModel.GetTypeInfo(parentExpression, cancellationToken).Type
End If
End If
If Not Object.Equals(expressionTypeInfo.Type, expressionTypeInfo.ConvertedType) Then
Return expressionTypeInfo.ConvertedType
End If
Dim parentEqualsValue = TryCast(parent, EqualsValueSyntax)
If parentEqualsValue IsNot Nothing Then
Dim returnedType = AsTypeInVariableDeclarator(parentEqualsValue.Parent, semanticModel)
If returnedType IsNot Nothing Then
Return returnedType
End If
End If
Dim parentForEach = TryCast(parent, ForEachStatementSyntax)
If parentForEach IsNot Nothing Then
Dim returnedType = AsTypeInVariableDeclarator(parentForEach.ControlVariable, semanticModel)
If returnedType IsNot Nothing Then
Return returnedType
End If
End If
Dim parentAssignmentStatement = TryCast(parent, AssignmentStatementSyntax)
If parentAssignmentStatement IsNot Nothing AndAlso parent.Kind = SyntaxKind.SimpleAssignmentStatement Then
Return semanticModel.GetTypeInfo(parentAssignmentStatement.Left, cancellationToken).Type
End If
Dim parentUnaryExpression = TryCast(parentExpression, UnaryExpressionSyntax)
If parentUnaryExpression IsNot Nothing AndAlso Not semanticModel.GetConversion(expression, cancellationToken).IsUserDefined Then
Dim parentTypeInfo = semanticModel.GetTypeInfo(parentUnaryExpression, cancellationToken)
Return GetOuterCastType(parentUnaryExpression, parentTypeInfo, semanticModel, cancellationToken)
End If
Dim parentTernaryConditional = TryCast(parent, TernaryConditionalExpressionSyntax)
If parentTernaryConditional IsNot Nothing AndAlso
parentTernaryConditional.Condition IsNot expression Then
Dim otherExpression = If(parentTernaryConditional.WhenTrue Is expression,
parentTernaryConditional.WhenFalse,
parentTernaryConditional.WhenTrue)
Return semanticModel.GetTypeInfo(otherExpression, cancellationToken).Type
End If
Dim parentSimpleArgument = TryCast(parent, SimpleArgumentSyntax)
If TypeOf parentSimpleArgument?.Expression Is CastExpressionSyntax OrElse
TypeOf parentSimpleArgument?.Expression Is PredefinedCastExpressionSyntax Then
Return semanticModel.GetTypeInfo(parentSimpleArgument.Expression, cancellationToken).Type
End If
Return expressionTypeInfo.ConvertedType
End Function
Private Shared Function GetSpeculatedExpressionToOuterTypeConversion(speculationAnalyzer As SpeculationAnalyzer, speculatedExpression As ExpressionSyntax, outerSpeculatedExpression As ExpressionSyntax, cancellationToken As CancellationToken, <Out> ByRef speculatedExpressionOuterType As ITypeSymbol) As Conversion
Dim innerSpeculatedExpression = speculatedExpression.WalkDownParentheses()
Dim typeInfo = speculationAnalyzer.SpeculativeSemanticModel.GetTypeInfo(innerSpeculatedExpression, cancellationToken)
Dim conv = speculationAnalyzer.SpeculativeSemanticModel.GetConversion(innerSpeculatedExpression, cancellationToken)
If Not conv.IsIdentity OrElse Not Object.Equals(typeInfo.Type, typeInfo.ConvertedType) Then
speculatedExpressionOuterType = typeInfo.ConvertedType
Return conv
End If
speculatedExpression = speculatedExpression.WalkUpParentheses()
typeInfo = speculationAnalyzer.SpeculativeSemanticModel.GetTypeInfo(speculatedExpression, cancellationToken)
speculatedExpressionOuterType = GetOuterCastType(speculatedExpression, typeInfo, speculationAnalyzer.SpeculativeSemanticModel, cancellationToken)
If speculatedExpressionOuterType Is Nothing OrElse outerSpeculatedExpression.IsParentKind(SyntaxKind.SimpleArgument) Then
Return Nothing
End If
Return speculationAnalyzer.SpeculativeSemanticModel.ClassifyConversion(speculatedExpression, speculatedExpressionOuterType)
End Function
Private Shared Function AsTypeInVariableDeclarator(node As SyntaxNode, semanticModel As SemanticModel) As ITypeSymbol
If node Is Nothing Then
Return Nothing
End If
Dim variableDeclarator = TryCast(node, VariableDeclaratorSyntax)
If variableDeclarator IsNot Nothing Then
Dim asClause = TryCast(variableDeclarator.AsClause, SimpleAsClauseSyntax)
If (asClause IsNot Nothing) Then
Return semanticModel.GetTypeInfo(asClause.Type).Type
End If
End If
Return Nothing
End Function
Private Function IsStartOfExecutableStatement() As Boolean
Dim parentExpression = _castNode.WalkUpParentheses()
Dim parentStatement = parentExpression.FirstAncestorOrSelf(Of ExecutableStatementSyntax)()
If parentStatement Is Nothing Then
Return False
End If
' If we are assuming that a call keyword will be inserted, then
' we can assume that we are not at the start of the part executable
' statement if it's a Call statement.
If _assumeCallKeyword AndAlso
parentStatement.IsKind(SyntaxKind.ExpressionStatement) AndAlso DirectCast(parentStatement, ExpressionStatementSyntax).Expression.IsKind(SyntaxKind.InvocationExpression) Then
Return False
End If
Return parentStatement.GetFirstToken() = parentExpression.GetFirstToken()
End Function
Private Function ExpressionCanStartExecutableStatement() As Boolean
Dim innerExpression = _castExpressionNode.WalkDownParentheses()
Return TypeOf innerExpression Is CastExpressionSyntax OrElse
TypeOf innerExpression Is PredefinedCastExpressionSyntax
End Function
Private Function IsUnnecessary() As Boolean
Dim speculationAnalyzer = New SpeculationAnalyzer(_castNode, _castExpressionNode, _semanticModel, _cancellationToken,
skipVerificationForReplacedNode:=True, failOnOverloadResolutionFailuresInOriginalCode:=True)
' First, check to see if the node ultimately parenting this cast has any
' syntax errors. If so, we bail.
If speculationAnalyzer.SemanticRootOfOriginalExpression.ContainsDiagnostics() Then
Return False
End If
If IsStartOfExecutableStatement() AndAlso Not ExpressionCanStartExecutableStatement() Then
Return False
End If
Dim castTypeInfo = _semanticModel.GetTypeInfo(_castNode, _cancellationToken)
Dim castType = castTypeInfo.Type
If castType Is Nothing OrElse castType.IsErrorType() Then
Return False
End If
Dim castExpressionType As ITypeSymbol
If _castExpressionNode.Kind = SyntaxKind.CollectionInitializer Then
' Get type of the array literal in context without the target type
castExpressionType = _semanticModel.GetSpeculativeTypeInfo(_castExpressionNode.SpanStart, _castExpressionNode, SpeculativeBindingOption.BindAsExpression).ConvertedType
Else
castExpressionType = _semanticModel.GetTypeInfo(_castExpressionNode, _cancellationToken).Type
End If
If castExpressionType IsNot Nothing AndAlso castExpressionType.IsErrorType() Then
Return False
End If
If CastPassedToParamArrayDefinitelyCantBeRemoved(castType) Then
Return False
End If
' A casts to object can always be removed from an expression inside of an interpolation, since it'll be converted to object
' in order to call string.Format(...) anyway.
If If(castType?.SpecialType = SpecialType.System_Object, False) AndAlso
_castNode.WalkUpParentheses().IsParentKind(SyntaxKind.Interpolation) Then
Return True
End If
' If removing the cast will result in a change in semantics of any of the parenting nodes, we won't remove it.
' We do this even for identity casts in case removing that cast might affect type inference.
If speculationAnalyzer.ReplacementChangesSemantics() Then
Return False
End If
Dim expressionToCastType = _semanticModel.ClassifyConversion(_castNode.SpanStart, _castExpressionNode, castType)
If expressionToCastType.IsIdentity Then
' Simple case: If the conversion from the inner expression to the cast type is identity,
' the cast can be removed.
Return True
ElseIf expressionToCastType.IsNarrowing AndAlso expressionToCastType.IsReference Then
' If the conversion from the inner expression to the cast type is narrowing reference conversion,
' the cast cannot be removed.
Return False
End If
Dim outerType = GetOuterCastType(_castNode, castTypeInfo, _semanticModel, _cancellationToken)
If outerType IsNot Nothing Then
Dim castToOuterType As Conversion = _semanticModel.ClassifyConversion(_castNode.SpanStart, _castNode, outerType)
Dim expressionToOuterType As Conversion
Dim speculatedExpressionOuterType As ITypeSymbol = Nothing
Dim outerSpeculatedExpression = _castNode.WalkUpParentheses()
If outerSpeculatedExpression.IsParentKind(SyntaxKind.DirectCastExpression) OrElse
outerSpeculatedExpression.IsParentKind(SyntaxKind.TryCastExpression) OrElse
outerSpeculatedExpression.IsParentKind(SyntaxKind.CTypeExpression) Then
speculatedExpressionOuterType = outerType
expressionToOuterType = _semanticModel.ClassifyConversion(_castExpressionNode.WalkDownParentheses(), speculatedExpressionOuterType)
Else
expressionToOuterType = GetSpeculatedExpressionToOuterTypeConversion(speculationAnalyzer, speculationAnalyzer.ReplacedExpression, outerSpeculatedExpression, _cancellationToken, speculatedExpressionOuterType)
If expressionToOuterType = Nothing AndAlso outerSpeculatedExpression.IsParentKind(SyntaxKind.SimpleArgument) Then
' If we are here we might be inside a SimpleArgument but it is
' not part of a ParamArray which is handled above.
speculatedExpressionOuterType = outerType
expressionToOuterType = _semanticModel.ClassifyConversion(_castExpressionNode.WalkDownParentheses(), speculatedExpressionOuterType)
End If
End If
' CONSIDER: Anonymous function conversions cannot be compared from different semantic models as lambda symbol comparison requires syntax tree equality. Should this be a compiler bug?
' For now, just revert back to computing expressionToOuterType using the original semantic model.
If expressionToOuterType.IsLambda Then
expressionToOuterType = _semanticModel.ClassifyConversion(_castNode.SpanStart, _castExpressionNode, outerType)
End If
' If there is an user-defined conversion from the expression to the cast type or the cast
' to the outer type, we need to make sure that the same user-defined conversion will be
' called if the cast is removed.
If castToOuterType.IsUserDefined OrElse expressionToCastType.IsUserDefined Then
Return (HaveSameUserDefinedConversion(expressionToCastType, expressionToOuterType) OrElse
HaveSameUserDefinedConversion(castToOuterType, expressionToOuterType)) AndAlso
(UserDefinedConversionIsAllowed(_castNode) AndAlso
Not expressionToCastType.IsNarrowing)
ElseIf expressionToOuterType.IsUserDefined Then
Return False
End If
If (expressionToOuterType.IsIdentity OrElse
(_castExpressionNode.Kind = SyntaxKind.CollectionInitializer AndAlso expressionToOuterType.IsWidening AndAlso speculatedExpressionOuterType.IsArrayType())) AndAlso
expressionToCastType.IsWidening Then
Return True
End If
If Not (castToOuterType.IsNullableValueType AndAlso castToOuterType.IsWidening) Then
Dim expressionToCastTypeIsWideningRefOrDefault As Boolean = expressionToCastType.IsWidening AndAlso (expressionToCastType.IsReference OrElse expressionToCastType.IsDefault)
Dim expressionToOuterTypeIsWideningRefOrDefault As Boolean = expressionToOuterType.IsWidening AndAlso (expressionToOuterType.IsReference OrElse expressionToOuterType.IsDefault)
If expressionToCastTypeIsWideningRefOrDefault AndAlso expressionToOuterTypeIsWideningRefOrDefault Then
If expressionToCastType.IsDefault Then
Return Not CastRemovalChangesDefaultValue(castType, outerType)
End If
Return True
ElseIf expressionToCastTypeIsWideningRefOrDefault OrElse expressionToOuterTypeIsWideningRefOrDefault Then
Return Equals(castType, speculatedExpressionOuterType)
End If
If expressionToCastType.IsWidening AndAlso expressionToCastType.IsLambda AndAlso
expressionToOuterType.IsWidening AndAlso expressionToOuterType.IsLambda Then
Return Not speculationAnalyzer.ReplacementChangesSemanticsOfUnchangedLambda(_castExpressionNode, speculationAnalyzer.ReplacedExpression)
End If
End If
If Not castToOuterType.IsValueType AndAlso castToOuterType = expressionToOuterType Then
If castToOuterType.IsNullableValueType Then
Return expressionToOuterType.IsWidening AndAlso
DirectCast(castExpressionType.OriginalDefinition, ITypeSymbol).SpecialType = SpecialType.System_Nullable_T
ElseIf expressionToCastType.IsWidening AndAlso expressionToCastType.IsNumeric AndAlso Not castToOuterType.IsIdentity Then
' Some widening numeric conversions can cause loss of precision and must not be removed.
Return Not IsRequiredWideningNumericConversion(castExpressionType, castType)
End If
Return True
End If
If castToOuterType.IsIdentity AndAlso
expressionToCastType = expressionToOuterType Then
If expressionToCastType.Exists Then
If expressionToCastType.IsWidening Then
Return True
End If
If expressionToCastType.IsNarrowing AndAlso
Not _semanticModel.OptionStrict = OptionStrict.On Then
Return True
End If
Else
Return True
End If
End If
If expressionToOuterType.IsIdentity AndAlso
castToOuterType.IsWidening AndAlso
castToOuterType.IsReference Then
Debug.Assert(Not (expressionToCastType.IsNarrowing AndAlso expressionToCastType.IsReference))
Return True
End If
End If
Return False
End Function
Private Shared Function HaveSameUserDefinedConversion(conversion1 As Conversion, conversion2 As Conversion) As Boolean
Return conversion1.IsUserDefined AndAlso conversion2.IsUserDefined AndAlso conversion1.MethodSymbol.Equals(conversion2.MethodSymbol)
End Function
Private Shared Function UserDefinedConversionIsAllowed(expression As ExpressionSyntax) As Boolean
expression = expression.WalkUpParentheses()
Dim parentNode = expression.Parent
If parentNode Is Nothing Then
Return False
End If
If parentNode.IsKind(SyntaxKind.ThrowStatement) Then
Return False
End If
Return True
End Function
Private Shared Function IsRequiredWideningNumericConversion(sourceType As ITypeSymbol, destinationType As ITypeSymbol) As Boolean
' VB Language Specification: Section 8.3 Numeric Conversions
' Conversions from UInteger, Integer, ULong, Long, or Decimal to Single or Double are rounded to the nearest Single or Double value.
' While this conversion may cause a loss of precision, it will never cause a loss of magnitude
Select Case destinationType.SpecialType
Case SpecialType.System_Single, SpecialType.System_Double
Select Case sourceType.SpecialType
Case SpecialType.System_UInt32, SpecialType.System_Int32,
SpecialType.System_UInt64, SpecialType.System_Int64,
SpecialType.System_Decimal
Return True
End Select
End Select
Return False
End Function
Private Shared Function CastRemovalChangesDefaultValue(castType As ITypeSymbol, outerType As ITypeSymbol) As Boolean
If castType.IsNumericType() Then
Return Not outerType.IsNumericType()
ElseIf castType.SpecialType = SpecialType.System_DateTime Then
Return Not outerType.SpecialType = SpecialType.System_DateTime
ElseIf castType.SpecialType = SpecialType.System_Boolean Then
Return Not (outerType.IsNumericType OrElse outerType.SpecialType = SpecialType.System_Boolean)
End If
If castType.OriginalDefinition?.SpecialType = SpecialType.System_Nullable_T Then
' Don't allow casts of Nothing to T? to be removed unless the outer type is T? or Object.
' Otherwise, Nothing will lose its "nullness" and get the default value of T.
'
' So, this is OK:
'
' Dim x As Object = DirectCast(Nothing, Integer?)
'
' But this is not:
'
' Dim x As Integer = DirectCast(Nothing, Integer?)
If castType.Equals(outerType) OrElse outerType.SpecialType = SpecialType.System_Object Then
Return False
End If
Return True
End If
Return False
End Function
Public Shared Function IsUnnecessary(
castNode As ExpressionSyntax,
castExpressionNode As ExpressionSyntax,
semanticModel As SemanticModel,
assumeCallKeyword As Boolean,
cancellationToken As CancellationToken
) As Boolean
Return New CastAnalyzer(castNode, castExpressionNode, semanticModel, assumeCallKeyword, cancellationToken).IsUnnecessary()
End Function
End Class
End Namespace
|