|
' 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.Runtime.InteropServices
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Namespace Microsoft.CodeAnalysis.VisualBasic
Partial Friend NotInheritable Class LocalRewriter
Private Function WrapInNullable(expr As BoundExpression, nullableType As TypeSymbol) As BoundExpression
Debug.Assert(nullableType.GetNullableUnderlyingType.IsSameTypeIgnoringAll(expr.Type))
Dim ctor = GetNullableMethod(expr.Syntax, nullableType, SpecialMember.System_Nullable_T__ctor)
If ctor IsNot Nothing Then
Return New BoundObjectCreationExpression(expr.Syntax,
ctor,
ImmutableArray.Create(expr),
Nothing,
nullableType)
End If
Return New BoundBadExpression(expr.Syntax, LookupResultKind.NotReferencable, ImmutableArray(Of Symbol).Empty, ImmutableArray.Create(expr), nullableType, hasErrors:=True)
End Function
''' <summary>
''' Splits nullable operand into a hasValueExpression and an expression that represents underlying value (returned).
'''
''' Underlying value can be called after calling hasValueExpr without duplicated side-effects.
''' Note that hasValueExpr is guaranteed to have NO SIDE-EFFECTS, while result value is
''' expected to be called exactly ONCE. That is the normal pattern in operator lifting.
'''
''' All necessary temps and side-effecting initializations are appended to temps and inits
''' </summary>
Private Function ProcessNullableOperand(operand As BoundExpression,
<Out> ByRef hasValueExpr As BoundExpression,
ByRef temps As ArrayBuilder(Of LocalSymbol),
ByRef inits As ArrayBuilder(Of BoundExpression),
doNotCaptureLocals As Boolean) As BoundExpression
Return ProcessNullableOperand(operand, hasValueExpr, temps, inits, doNotCaptureLocals, HasValue(operand))
End Function
Private Function ProcessNullableOperand(operand As BoundExpression,
<Out> ByRef hasValueExpr As BoundExpression,
ByRef temps As ArrayBuilder(Of LocalSymbol),
ByRef inits As ArrayBuilder(Of BoundExpression),
doNotCaptureLocals As Boolean,
operandHasValue As Boolean) As BoundExpression
Debug.Assert(Not HasNoValue(operand), "processing nullable operand when it is known to be null")
If operandHasValue Then
operand = NullableValueOrDefault(operand)
End If
Dim captured = CaptureNullableIfNeeded(operand, temps, inits, doNotCaptureLocals)
If operandHasValue Then
hasValueExpr = New BoundLiteral(operand.Syntax, ConstantValue.True, Me.GetSpecialType(SpecialType.System_Boolean))
Return captured
End If
hasValueExpr = NullableHasValue(captured)
Return NullableValueOrDefault(captured)
End Function
' Right operand could be a method that takes Left operand byref. Ex: " local And TakesArgByref(local) "
' So in general we must capture Left even if it is a local.
' however in many case we do not need that.
Private Shared Function RightCantChangeLeftLocal(left As BoundExpression, right As BoundExpression) As Boolean
' TODO: in most cases right operand does not change value of the left one
' we could be smarter than this.
Return right.Kind = BoundKind.Local OrElse
right.Kind = BoundKind.Parameter
End Function
''' <summary>
''' Returns a NOT-SIDE-EFFECTING expression that represents results of the operand
''' If such transformation requires a temp, the temp and its initializing expression
''' are returned in temp/init
''' </summary>
Private Function CaptureNullableIfNeeded(operand As BoundExpression,
<Out> ByRef temp As SynthesizedLocal,
<Out> ByRef init As BoundExpression,
doNotCaptureLocals As Boolean) As BoundExpression
temp = Nothing
init = Nothing
If operand.IsConstant Then
Return operand
End If
If doNotCaptureLocals Then
If operand.Kind = BoundKind.Local AndAlso Not DirectCast(operand, BoundLocal).LocalSymbol.IsByRef Then
Return operand
End If
If operand.Kind = BoundKind.Parameter AndAlso Not DirectCast(operand, BoundParameter).ParameterSymbol.IsByRef Then
Return operand
End If
End If
' capture into local.
Return CaptureOperand(operand, temp, init)
End Function
Private Function CaptureOperand(operand As BoundExpression, <Out> ByRef temp As SynthesizedLocal, <Out> ByRef init As BoundExpression) As BoundExpression
temp = New SynthesizedLocal(Me._currentMethodOrLambda, operand.Type, SynthesizedLocalKind.LoweringTemp)
Dim localAccess = New BoundLocal(operand.Syntax, temp, True, temp.Type)
init = New BoundAssignmentOperator(operand.Syntax, localAccess, operand, True, operand.Type)
Return localAccess.MakeRValue
End Function
Private Function CaptureNullableIfNeeded(
operand As BoundExpression,
<[In], Out> ByRef temps As ArrayBuilder(Of LocalSymbol),
<[In], Out> ByRef inits As ArrayBuilder(Of BoundExpression),
doNotCaptureLocals As Boolean
) As BoundExpression
Dim temp As SynthesizedLocal = Nothing
Dim init As BoundExpression = Nothing
Dim captured = CaptureNullableIfNeeded(operand, temp, init, doNotCaptureLocals)
If temp IsNot Nothing Then
temps = If(temps, ArrayBuilder(Of LocalSymbol).GetInstance)
temps.Add(temp)
Debug.Assert(init IsNot Nothing)
inits = If(inits, ArrayBuilder(Of BoundExpression).GetInstance)
inits.Add(init)
Else
Debug.Assert(captured Is operand)
End If
Return captured
End Function
''' <summary>
''' Returns expression that -
''' a) evaluates the operand if needed
''' b) produces it's ValueOrDefault.
''' The helper is familiar with wrapping expressions and will go directly after the value
''' skipping wrap/unwrap steps.
''' </summary>
Private Function NullableValueOrDefault(expr As BoundExpression, Optional isOptional As Boolean = False) As BoundExpression
Debug.Assert(expr.Type.IsNullableType)
' check if we are not getting value from freshly constructed nullable
' no need to wrap/unwrap it then.
Select Case expr.Kind
Case BoundKind.ObjectCreationExpression
Dim objectCreation = DirectCast(expr, BoundObjectCreationExpression)
' passing one argument means we are calling New Nullable<T>(arg)
If objectCreation.Arguments.Length = 1 Then
Return objectCreation.Arguments(0)
End If
Case BoundKind.Conversion
Dim conversion = DirectCast(expr, BoundConversion)
If IsConversionFromUnderlyingToNullable(conversion) Then
Return conversion.Operand
End If
Case Else
Debug.Assert(Not HasValue(expr))
If Not _inExpressionLambda AndAlso expr.Type.IsNullableOfBoolean() Then
Dim whenNotNull As BoundExpression = Nothing
Dim whenNull As BoundExpression = Nothing
If IsConditionalAccess(expr, whenNotNull, whenNull) AndAlso HasNoValue(whenNull) Then
Debug.Assert(Not HasNoValue(whenNotNull))
Dim valueOrDefault = NullableValueOrDefault(whenNotNull, isOptional)
If valueOrDefault Is Nothing Then
Debug.Assert(isOptional)
Return Nothing
End If
Return UpdateConditionalAccess(expr,
valueOrDefault,
New BoundLiteral(expr.Syntax, ConstantValue.False, expr.Type.GetNullableUnderlyingType()))
End If
End If
End Select
Dim getValueOrDefaultMethod = GetNullableMethod(expr.Syntax, expr.Type, SpecialMember.System_Nullable_T_GetValueOrDefault, isOptional)
If getValueOrDefaultMethod IsNot Nothing Then
Return New BoundCall(expr.Syntax,
getValueOrDefaultMethod,
Nothing,
expr,
ImmutableArray(Of BoundExpression).Empty,
Nothing,
isLValue:=False,
suppressObjectClone:=True,
type:=getValueOrDefaultMethod.ReturnType)
End If
Return If(isOptional, Nothing, New BoundBadExpression(expr.Syntax, LookupResultKind.NotReferencable, ImmutableArray(Of Symbol).Empty, ImmutableArray.Create(expr), expr.Type.GetNullableUnderlyingType(), hasErrors:=True))
End Function
Private Function NullableValueOrDefaultOpt(expr As BoundExpression, defaultValue As BoundExpression) As BoundExpression
Debug.Assert(expr.Type.IsNullableType)
Dim getValueOrDefaultWithDefaultValueMethod = GetNullableMethod(expr.Syntax, expr.Type, SpecialMember.System_Nullable_T_GetValueOrDefaultDefaultValue, isOptional:=True)
If getValueOrDefaultWithDefaultValueMethod IsNot Nothing Then
Return New BoundCall(expr.Syntax,
getValueOrDefaultWithDefaultValueMethod,
Nothing,
expr,
ImmutableArray.Create(defaultValue),
Nothing,
isLValue:=False,
suppressObjectClone:=True,
type:=getValueOrDefaultWithDefaultValueMethod.ReturnType)
End If
Return Nothing
End Function
Private Shared Function IsConversionFromUnderlyingToNullable(conversion As BoundConversion) As Boolean
Return (conversion.ConversionKind And (ConversionKind.Widening Or ConversionKind.Nullable Or ConversionKind.UserDefined)) = (ConversionKind.Widening Or ConversionKind.Nullable) AndAlso
conversion.Type.GetNullableUnderlyingType().Equals(conversion.Operand.Type, TypeCompareKind.AllIgnoreOptionsForVB)
End Function
Private Function NullableValue(expr As BoundExpression) As BoundExpression
Debug.Assert(expr.Type.IsNullableType)
If HasValue(expr) Then
Return NullableValueOrDefault(expr)
End If
Dim getValueMethod As MethodSymbol = GetNullableMethod(expr.Syntax, expr.Type, SpecialMember.System_Nullable_T_get_Value)
If getValueMethod IsNot Nothing Then
Return New BoundCall(expr.Syntax,
getValueMethod,
Nothing,
expr,
ImmutableArray(Of BoundExpression).Empty,
Nothing,
isLValue:=False,
suppressObjectClone:=True,
type:=getValueMethod.ReturnType)
End If
Return New BoundBadExpression(expr.Syntax, LookupResultKind.NotReferencable, ImmutableArray(Of Symbol).Empty, ImmutableArray.Create(expr), expr.Type.GetNullableUnderlyingType(), hasErrors:=True)
End Function
''' <summary>
''' Evaluates expr and calls HasValue on it.
''' </summary>
Private Function NullableHasValue(expr As BoundExpression) As BoundExpression
Debug.Assert(expr.Type.IsNullableType)
' when we statically know if expr HasValue we may skip
' evaluation depending on context.
Debug.Assert(Not HasValue(expr))
Debug.Assert(Not HasNoValue(expr))
Dim hasValueMethod As MethodSymbol = GetNullableMethod(expr.Syntax, expr.Type, SpecialMember.System_Nullable_T_get_HasValue)
If hasValueMethod IsNot Nothing Then
Return New BoundCall(expr.Syntax,
hasValueMethod,
Nothing,
expr,
ImmutableArray(Of BoundExpression).Empty,
Nothing,
isLValue:=False,
suppressObjectClone:=True,
type:=hasValueMethod.ReturnType)
End If
Return New BoundBadExpression(expr.Syntax, LookupResultKind.NotReferencable, ImmutableArray(Of Symbol).Empty, ImmutableArray.Create(expr),
Me.Compilation.GetSpecialType(SpecialType.System_Boolean), hasErrors:=True)
End Function
Private Shared Function NullableNull(syntax As SyntaxNode, nullableType As TypeSymbol) As BoundExpression
Debug.Assert(nullableType.IsNullableType)
Return New BoundObjectCreationExpression(syntax,
Nothing,
ImmutableArray(Of BoundExpression).Empty,
Nothing,
nullableType)
End Function
''' <summary>
''' Checks that candidate Null expression is a simple expression that produces Null of the desired type
''' (not a conversion or anything like that) and returns it.
''' Otherwise creates "New T?()" expression.
''' </summary>
Private Shared Function NullableNull(candidateNullExpression As BoundExpression,
type As TypeSymbol) As BoundExpression
Debug.Assert(HasNoValue(candidateNullExpression))
' in case if the expression is any more complicated than just creating a Null
' simplify it. This may happen if HasNoValue gets smarter and can
' detect situations other than "New T?()"
If (Not type.IsSameTypeIgnoringAll(candidateNullExpression.Type)) OrElse
candidateNullExpression.Kind <> BoundKind.ObjectCreationExpression Then
Return NullableNull(candidateNullExpression.Syntax, type)
End If
Return candidateNullExpression
End Function
Private Function NullableFalse(syntax As SyntaxNode, nullableOfBoolean As TypeSymbol) As BoundExpression
Debug.Assert(nullableOfBoolean.IsNullableOfBoolean)
Dim booleanType = nullableOfBoolean.GetNullableUnderlyingType
Return WrapInNullable(New BoundLiteral(syntax, ConstantValue.False, booleanType), nullableOfBoolean)
End Function
Private Function NullableTrue(syntax As SyntaxNode, nullableOfBoolean As TypeSymbol) As BoundExpression
Debug.Assert(nullableOfBoolean.IsNullableOfBoolean)
Dim booleanType = nullableOfBoolean.GetNullableUnderlyingType
Return WrapInNullable(New BoundLiteral(syntax, ConstantValue.True, booleanType), nullableOfBoolean)
End Function
Private Function GetNullableMethod(syntax As SyntaxNode, nullableType As TypeSymbol, member As SpecialMember, Optional isOptional As Boolean = False) As MethodSymbol
Dim method As MethodSymbol = Nothing
If TryGetSpecialMember(method, member, syntax, isOptional) Then
Dim substitutedType = DirectCast(nullableType, SubstitutedNamedType)
Return DirectCast(substitutedType.GetMemberForDefinition(method), MethodSymbol)
End If
Return Nothing
End Function
Private Function NullableOfBooleanValue(syntax As SyntaxNode, isTrue As Boolean, nullableOfBoolean As TypeSymbol) As BoundExpression
If isTrue Then
Return NullableTrue(syntax, nullableOfBoolean)
Else
Return NullableFalse(syntax, nullableOfBoolean)
End If
End Function
''' <summary>
''' returns true when expression has NO SIDE-EFFECTS and is known to produce nullable NULL
''' </summary>
Private Shared Function HasNoValue(expr As BoundExpression) As Boolean
Debug.Assert(expr.Type.IsNullableType)
If expr.Kind = BoundKind.ObjectCreationExpression Then
Dim objCreation = DirectCast(expr, BoundObjectCreationExpression)
' Nullable<T> has only one ctor with parameters and only that one sets hasValue = true
Return objCreation.Arguments.Length = 0
End If
' by default we do not know
Return False
End Function
''' <summary>
''' Returns true when expression is known to produce nullable NOT-NULL
''' NOTE: unlike HasNoValue case, HasValue expressions may have side-effects.
''' </summary>
Private Shared Function HasValue(expr As BoundExpression) As Boolean
Debug.Assert(expr.Type.IsNullableType)
Select Case expr.Kind
Case BoundKind.ObjectCreationExpression
Dim objCreation = DirectCast(expr, BoundObjectCreationExpression)
' Nullable<T> has only one ctor with parameters and only that one sets hasValue = true
Return objCreation.Arguments.Length <> 0
Case BoundKind.Conversion
If IsConversionFromUnderlyingToNullable(DirectCast(expr, BoundConversion)) Then
Return True
End If
End Select
' by default we do not know
Return False
End Function
''' <summary>
''' Helper to generate binary expressions.
''' Performs some trivial constant folding.
''' TODO: Perhaps belong to a different file
''' </summary>
Private Function MakeBinaryExpression(syntax As SyntaxNode,
binaryOpKind As BinaryOperatorKind,
left As BoundExpression,
right As BoundExpression,
isChecked As Boolean,
resultType As TypeSymbol) As BoundExpression
Debug.Assert(Not left.Type.IsNullableType)
Debug.Assert(Not right.Type.IsNullableType)
Dim intOverflow As Boolean = False
Dim divideByZero As Boolean = False
Dim lengthOutOfLimit As Boolean = False
Dim constant = OverloadResolution.TryFoldConstantBinaryOperator(binaryOpKind, left, right, resultType, intOverflow, divideByZero, lengthOutOfLimit)
If constant IsNot Nothing AndAlso
Not divideByZero AndAlso
Not (intOverflow And isChecked) AndAlso
Not lengthOutOfLimit Then
Debug.Assert(Not constant.IsBad)
Return New BoundLiteral(syntax, constant, resultType)
End If
Select Case binaryOpKind
Case BinaryOperatorKind.Subtract
If right.IsDefaultValueConstant Then
Return left
End If
Case BinaryOperatorKind.Add,
BinaryOperatorKind.Or,
BinaryOperatorKind.OrElse
' if one of operands is trivial, return the other one
If left.IsDefaultValueConstant Then
Return right
End If
If right.IsDefaultValueConstant Then
Return left
End If
' if one of operands is True, evaluate the other and return the True one
If left.IsTrueConstant Then
Return MakeSequence(right, left)
End If
If right.IsTrueConstant Then
Return MakeSequence(left, right)
End If
Case BinaryOperatorKind.And,
BinaryOperatorKind.AndAlso,
BinaryOperatorKind.Multiply
' if one of operands is trivial, evaluate the other and return the trivial one
If left.IsDefaultValueConstant Then
Return MakeSequence(right, left)
End If
If right.IsDefaultValueConstant Then
Return MakeSequence(left, right)
End If
' if one of operands is True, return the other one
If left.IsTrueConstant Then
Return right
End If
If right.IsTrueConstant Then
Return left
End If
Case BinaryOperatorKind.Equals
If left.IsTrueConstant Then
Return right
End If
If right.IsTrueConstant Then
Return left
End If
Case BinaryOperatorKind.NotEquals
If left.IsFalseConstant Then
Return right
End If
If right.IsFalseConstant Then
Return left
End If
End Select
Return TransformRewrittenBinaryOperator(New BoundBinaryOperator(syntax, binaryOpKind, left, right, isChecked, resultType))
End Function
''' <summary>
''' Simpler helper for binary expressions.
''' When operand are boolean, the result type is same as operand's and is never checked
''' so do not need to pass that in.
''' </summary>
Private Function MakeBooleanBinaryExpression(syntax As SyntaxNode,
binaryOpKind As BinaryOperatorKind,
left As BoundExpression,
right As BoundExpression) As BoundExpression
Debug.Assert(TypeSymbol.Equals(left.Type, right.Type, TypeCompareKind.ConsiderEverything))
Debug.Assert(left.Type.IsBooleanType)
Return MakeBinaryExpression(syntax, binaryOpKind, left, right, False, left.Type)
End Function
Private Shared Function MakeNullLiteral(syntax As SyntaxNode, type As TypeSymbol) As BoundLiteral
Return New BoundLiteral(syntax, ConstantValue.Nothing, type)
End Function
''' <summary>
''' Takes two expressions and makes sequence.
''' </summary>
Private Shared Function MakeSequence(first As BoundExpression, second As BoundExpression) As BoundExpression
Return MakeSequence(second.Syntax, first, second)
End Function
''' <summary>
''' Takes two expressions and makes sequence.
''' </summary>
Private Shared Function MakeSequence(syntax As SyntaxNode,
first As BoundExpression,
second As BoundExpression) As BoundExpression
Dim sideeffects = GetSideeffects(first)
If sideeffects Is Nothing Then
Return second
End If
Return New BoundSequence(syntax,
ImmutableArray(Of LocalSymbol).Empty,
ImmutableArray.Create(sideeffects),
second,
second.Type)
End Function
''' <summary>
''' Takes two expressions and makes sequence.
''' </summary>
Private Function MakeTernaryConditionalExpression(syntax As SyntaxNode,
condition As BoundExpression,
whenTrue As BoundExpression,
whenFalse As BoundExpression) As BoundExpression
Debug.Assert(condition.Type.IsBooleanType, "ternary condition must be boolean")
Debug.Assert(whenTrue.Type.IsSameTypeIgnoringAll(whenFalse.Type), "ternary branches must have same types")
Dim ifConditionConst = condition.ConstantValueOpt
If ifConditionConst IsNot Nothing Then
Return MakeSequence(syntax, condition, If(ifConditionConst Is ConstantValue.True, whenTrue, whenFalse))
End If
Return TransformRewrittenTernaryConditionalExpression(New BoundTernaryConditionalExpression(syntax, condition, whenTrue, whenFalse, Nothing, whenTrue.Type))
End Function
''' <summary>
''' Returns an expression that can be used instead of the original one when
''' we want to run the expression for side-effects only (i.e. we intend to ignore result).
''' </summary>
Private Shared Function GetSideeffects(operand As BoundExpression) As BoundExpression
If operand.IsConstant Then
Return Nothing
End If
Select Case operand.Kind
Case BoundKind.Local,
BoundKind.Parameter
Return Nothing
Case BoundKind.ObjectCreationExpression
If operand.Type.IsNullableType Then
Dim objCreation = DirectCast(operand, BoundObjectCreationExpression)
Dim args = objCreation.Arguments
If args.Length = 0 Then
Return Nothing
Else
Return GetSideeffects(args(0))
End If
End If
End Select
Return operand
End Function
End Class
End Namespace
|