/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_NullableHelpers.vb
583 строки
27 KB
DoctorKrolic
Optimize `T? ?? <expr>` to `T.GetValueOrDefault(<expr>)` for a side-effectless `<expr>` where `T?` is a nullable value type (#70199)
20 окт 2023, 16:36
Не верифицирован
20 окт 2023, 16:36
4414e2f
Код
Авторство
О чём код?
' 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