/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/VisualBasic/Portable/Lowering/LocalRewriter/LocalRewriter_ConditionalExpressions.vb
352 строки
19 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 Microsoft.CodeAnalysis.VisualBasic.Symbols Namespace Microsoft.CodeAnalysis.VisualBasic Partial Friend NotInheritable Class LocalRewriter Public Overrides Function VisitBinaryConditionalExpression(node As BoundBinaryConditionalExpression) As BoundNode If Me._inExpressionLambda Then ' If we are inside expression lambda we want to keep binary conditional expression Return RewriteBinaryConditionalExpressionInExpressionLambda(node) End If If node.TestExpression.Type IsNot Nothing AndAlso node.TestExpression.Type.IsNullableType Then Return RewriteNullableBinaryConditionalExpression(node) End If Dim convertedTestExpression As BoundExpression = node.ConvertedTestExpression If convertedTestExpression Is Nothing Then Debug.Assert(node.TestExpressionPlaceholder Is Nothing) Return TransformReferenceOrUnconstrainedRewrittenBinaryConditionalExpression(MyBase.VisitBinaryConditionalExpression(node)) End If If convertedTestExpression.Kind = BoundKind.Conversion Then Dim boundConversion = DirectCast(node.ConvertedTestExpression, BoundConversion) Dim conversion = boundConversion.ConversionKind If Conversions.IsWideningConversion(conversion) AndAlso Conversions.IsCLRPredefinedConversion(conversion) AndAlso ((conversion And ConversionKind.TypeParameter) = 0) Then Debug.Assert(boundConversion.Operand Is If(node.TestExpressionPlaceholder, node.TestExpression)) ' We can ignore conversion in this case Return TransformReferenceOrUnconstrainedRewrittenBinaryConditionalExpression( node.Update(VisitExpressionNode(node.TestExpression), Nothing, Nothing, VisitExpressionNode(node.ElseExpression), node.ConstantValueOpt, node.Type)) End If End If ' Rewrite binary conditional expression using ternary conditional expression Dim placeholder As BoundValuePlaceholderBase = node.TestExpressionPlaceholder ' Rewrite test expression and create a local to capture the value if needed Dim rewrittenTestExpression = VisitExpressionNode(node.TestExpression) Dim rewrittenTestExpressionType As TypeSymbol = rewrittenTestExpression.Type ' NOTE: we create a temp variable if only it is really needed; ' locals and parameters don't need it Dim tempVariableSymbol As SynthesizedLocal = Nothing Dim placeholderSubstitute As BoundExpression = Nothing Select Case rewrittenTestExpression.Kind Case BoundKind.Local, BoundKind.Parameter, BoundKind.Literal placeholderSubstitute = rewrittenTestExpression Case Else ' create a temp variable tempVariableSymbol = New SynthesizedLocal(Me._currentMethodOrLambda, rewrittenTestExpressionType, SynthesizedLocalKind.LoweringTemp) ' temp variable reference placeholderSubstitute = New BoundLocal(rewrittenTestExpression.Syntax, tempVariableSymbol, isLValue:=False, type:=rewrittenTestExpressionType) End Select ' Rewrite test expression conversion If placeholder IsNot Nothing Then ' substitute placeholder with temp created or a local/parameter AddPlaceholderReplacement(placeholder, placeholderSubstitute) End If Dim rewrittenConvertedTestExpression = VisitExpressionNode(node.ConvertedTestExpression) If placeholder IsNot Nothing Then RemovePlaceholderReplacement(placeholder) End If Dim ternaryConditionalExpression As BoundExpression = placeholderSubstitute If tempVariableSymbol IsNot Nothing Then ' if there is a temp local assign a value to it ternaryConditionalExpression = New BoundAssignmentOperator(rewrittenTestExpression.Syntax, left:=New BoundLocal(rewrittenTestExpression.Syntax, tempVariableSymbol, isLValue:=True, type:=rewrittenTestExpressionType), right:=rewrittenTestExpression, suppressObjectClone:=True, type:=rewrittenTestExpressionType) End If ' NOTE: newTestExpression is not actually a boolean expression, but it is of a reference ' type and has 'boolean semantic' (Nothing = False, True otherwise) Dim result As BoundExpression = TransformRewrittenTernaryConditionalExpression( New BoundTernaryConditionalExpression(node.Syntax, condition:=ternaryConditionalExpression, whenTrue:=rewrittenConvertedTestExpression, whenFalse:=VisitExpressionNode(node.ElseExpression), constantValueOpt:=node.ConstantValueOpt, type:=node.Type)) If tempVariableSymbol Is Nothing Then Return result End If Return New BoundSequence(result.Syntax, locals:=ImmutableArray.Create(Of LocalSymbol)(tempVariableSymbol), sideEffects:=ImmutableArray(Of BoundExpression).Empty, valueOpt:=result, type:=result.Type) End Function Private Function RewriteBinaryConditionalExpressionInExpressionLambda(node As BoundBinaryConditionalExpression) As BoundExpression ' If we are inside expression lambda we want to keep binary conditional expression Dim testExpression As BoundExpression = node.TestExpression Dim testExpressionType As TypeSymbol = testExpression.Type Dim rewrittenTestExpression As BoundExpression = VisitExpression(testExpression) Debug.Assert(TypeSymbol.Equals(testExpressionType, rewrittenTestExpression.Type, TypeCompareKind.ConsiderEverything)) Dim rewrittenWhenTrue As BoundExpression = Nothing Dim convertedTestExpression As BoundExpression = node.ConvertedTestExpression If convertedTestExpression Is Nothing Then If Not testExpressionType.IsNullableOfBoolean AndAlso testExpressionType.IsNullableType Then rewrittenWhenTrue = New BoundConversion(rewrittenTestExpression.Syntax, rewrittenTestExpression, ConversionKind.WideningNullable, False, False, testExpressionType.GetNullableUnderlyingTypeOrSelf) End If Else Debug.Assert(node.TestExpressionPlaceholder Is Nothing OrElse TypeSymbol.Equals(node.TestExpressionPlaceholder.Type, testExpressionType.GetNullableUnderlyingTypeOrSelf, TypeCompareKind.ConsiderEverything)) rewrittenWhenTrue = VisitExpressionNode(convertedTestExpression, node.TestExpressionPlaceholder, If(testExpressionType.IsNullableType, New BoundConversion(rewrittenTestExpression.Syntax, rewrittenTestExpression, ConversionKind.WideningNullable, False, False, testExpressionType.GetNullableUnderlyingTypeOrSelf), rewrittenTestExpression)) End If Dim rewrittenWhenFalse As BoundExpression = VisitExpression(node.ElseExpression) Return node.Update(rewrittenTestExpression, rewrittenWhenTrue, Nothing, rewrittenWhenFalse, node.ConstantValueOpt, node.Type) End Function Private Shared Function TransformReferenceOrUnconstrainedRewrittenBinaryConditionalExpression(node As BoundNode) As BoundNode Return If(node.Kind <> BoundKind.BinaryConditionalExpression, node, TransformReferenceOrUnconstrainedRewrittenBinaryConditionalExpression(DirectCast(node, BoundBinaryConditionalExpression))) End Function Private Shared Function TransformReferenceOrUnconstrainedRewrittenBinaryConditionalExpression(node As BoundBinaryConditionalExpression) As BoundExpression Debug.Assert(node.ConvertedTestExpression Is Nothing) ' Those should be rewritten by now Debug.Assert(node.TestExpressionPlaceholder Is Nothing) ' NOTE: C# implementation rewrites the coalesce expression to handle the bug ' related to IF(DirectCast(<class1>, I1), DirectCast(<class2>, I1)) ' VB handles this case in Emitter If node.HasErrors Then Return node End If Dim testExpr = node.TestExpression Dim elseExpr = node.ElseExpression ' Nullable value types should have been handled earlier in lowering, so we can only have reference types and ' unconstrained type parameters at this point Debug.Assert(testExpr.IsNothingLiteral OrElse Not testExpr.Type.IsValueType) ' TODO: Checking type equality of test and else is not strictly needed. ' Consider removing this requirement. If testExpr.IsConstant AndAlso (TypeSymbol.Equals(testExpr.Type, elseExpr.Type, TypeCompareKind.ConsiderEverything)) Then ' the only valid IF(...) with the first constant are: IF("abc", <expr>) or IF(Nothing, <expr>) If testExpr.ConstantValueOpt.IsNothing Then ' CASE: IF(Nothing, <expr>) ' Special case: just emit ElseExpression Return node.ElseExpression Else ' CASE: IF("abc", <expr>) ' Dominant type may be different from String, so add conversion Return testExpr End If End If Debug.Assert(Not testExpr.Type.IsValueType) Return node End Function Private Function RewriteNullableBinaryConditionalExpression(node As BoundBinaryConditionalExpression) As BoundNode ' == rewrite operands and check for trivial cases Dim rewrittenLeft = Me.VisitExpressionNode(node.TestExpression) If HasValue(rewrittenLeft) Then Return MakeResultFromNonNullLeft(rewrittenLeft, node.ConvertedTestExpression, node.TestExpressionPlaceholder) End If Dim rewrittenRight = Me.VisitExpressionNode(node.ElseExpression) If HasNoValue(rewrittenLeft) Then Return rewrittenRight End If Dim whenNotNull As BoundExpression = Nothing Dim whenNull As BoundExpression = Nothing If IsConditionalAccess(rewrittenLeft, whenNotNull, whenNull) Then If HasNoValue(whenNull) Then If HasValue(whenNotNull) Then Return UpdateConditionalAccess(rewrittenLeft, MakeResultFromNonNullLeft(whenNotNull, node.ConvertedTestExpression, node.TestExpressionPlaceholder), rewrittenRight) Else Debug.Assert(Not HasNoValue(whenNotNull)) ' Not optimizing for this case ' CONSIDER: We could do inlining when rewrittenRight.IsConstant End If End If End If If rewrittenLeft.Type.IsNullableType() AndAlso rewrittenRight.Type.IsSameTypeIgnoringAll(rewrittenLeft.Type.GetNullableUnderlyingType()) _ Then ' Optimize If(left, right) to left.GetValueOrDefault() when left is T? and right is the default value of T If rewrittenRight.IsDefaultValue() Then Dim optimized = NullableValueOrDefault(rewrittenLeft, isOptional:=True) If optimized IsNot Nothing Then Return optimized End If End If ' Optimize If(left, right) to left.GetValueOrDefault(right) when left is T? and right is a side-effectless expression of type T If rewrittenRight.ConstantValueOpt IsNot Nothing OrElse Not If(TryCast(rewrittenRight, BoundLocal)?.LocalSymbol.IsRef, True) OrElse Not If(TryCast(rewrittenRight, BoundParameter)?.ParameterSymbol.IsByRef, True) Then Dim optimized = NullableValueOrDefaultOpt(rewrittenLeft, rewrittenRight) If optimized IsNot Nothing Then Return optimized End If End If End If '=== Rewrite binary conditional expression using ternary conditional expression Dim temp As SynthesizedLocal = Nothing Dim tempInit As BoundExpression = Nothing ' no need to capture locals since we will not ' evaluate anything between HasValue and ValueOrDefault Dim capturedleft As BoundExpression = CaptureNullableIfNeeded(rewrittenLeft, temp, tempInit, doNotCaptureLocals:=True) Dim condition As BoundExpression = NullableHasValue(If(tempInit, capturedleft)) Dim whenTrue As BoundExpression If node.ConvertedTestExpression Is Nothing Then whenTrue = NullableValueOrDefault(capturedleft) Else If capturedleft.Type.IsSameTypeIgnoringAll(node.ConvertedTestExpression.Type) Then ' Optimization whenTrue = capturedleft Else whenTrue = VisitExpressionNode(node.ConvertedTestExpression, node.TestExpressionPlaceholder, NullableValueOrDefault(capturedleft)) End If End If Dim result As BoundExpression = MakeTernaryConditionalExpression(node.Syntax, condition, whenTrue, rewrittenRight) ' if we used a temp, arrange a sequence for it If temp IsNot Nothing Then result = New BoundSequence(node.Syntax, ImmutableArray.Create(Of LocalSymbol)(temp), ImmutableArray(Of BoundExpression).Empty, result, result.Type) End If Return result End Function Private Function MakeResultFromNonNullLeft(rewrittenLeft As BoundExpression, convertedTestExpression As BoundExpression, testExpressionPlaceholder As BoundRValuePlaceholder) As BoundExpression If convertedTestExpression Is Nothing Then Return NullableValueOrDefault(rewrittenLeft) Else If rewrittenLeft.Type.IsSameTypeIgnoringAll(convertedTestExpression.Type) Then ' Optimization Return rewrittenLeft End If Return VisitExpressionNode(convertedTestExpression, testExpressionPlaceholder, NullableValueOrDefault(rewrittenLeft)) End If End Function Private Function VisitExpressionNode(node As BoundExpression, placeholder As BoundValuePlaceholderBase, placeholderSubstitute As BoundExpression) As BoundExpression If placeholder IsNot Nothing Then AddPlaceholderReplacement(placeholder, placeholderSubstitute) End If Dim result = VisitExpressionNode(node) If placeholder IsNot Nothing Then RemovePlaceholderReplacement(placeholder) End If Return result End Function Public Overrides Function VisitTernaryConditionalExpression(node As BoundTernaryConditionalExpression) As BoundNode Return TransformRewrittenTernaryConditionalExpression( DirectCast(MyBase.VisitTernaryConditionalExpression(node), BoundTernaryConditionalExpression)) End Function Private Shared Function TransformRewrittenTernaryConditionalExpression(node As BoundTernaryConditionalExpression) As BoundExpression If node.Condition.IsConstant AndAlso node.WhenTrue.IsConstant AndAlso node.WhenFalse.IsConstant Then ' This optimization be applies if only *all three* operands are constants!!! Debug.Assert(node.Condition.ConstantValueOpt.IsBoolean OrElse node.Condition.ConstantValueOpt.IsNothing OrElse node.Condition.ConstantValueOpt.IsString) Dim value As Boolean = If(node.Condition.ConstantValueOpt.IsBoolean, node.Condition.ConstantValueOpt.BooleanValue, node.Condition.ConstantValueOpt.IsString) Return If(value, node.WhenTrue, node.WhenFalse) End If Return node End Function End Class End Namespace