Skip to content

Commit

Permalink
VB: Do not use overload resolution while lowering interpolated strings (
Browse files Browse the repository at this point in the history
#75449)

Related to #74275.
  • Loading branch information
AlekseyTs authored Oct 15, 2024
1 parent 09012a1 commit 88c8ad9
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 217 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1618,7 +1618,9 @@ DoneWithDiagnostics:

If (convKind And ConversionKind.InterpolatedString) = ConversionKind.InterpolatedString Then
Debug.Assert(targetType.Equals(Compilation.GetWellKnownType(WellKnownType.System_IFormattable)) OrElse targetType.Equals(Compilation.GetWellKnownType(WellKnownType.System_FormattableString)))
Return New BoundConversion(tree, node, ConversionKind.InterpolatedString, False, isExplicit, targetType)
Return New BoundConversion(tree,
BindUnconvertedInterpolatedStringToFormattable(tree, node, targetType, diagnostics),
ConversionKind.InterpolatedString, False, isExplicit, targetType)
End If

Return node
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1400,6 +1400,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Throw ExceptionUtilities.UnexpectedValue(propertyAccess.AccessKind)
End Select

ElseIf expr.Kind = BoundKind.InterpolatedStringExpression Then

expr = BindUnconvertedInterpolatedStringToString(DirectCast(expr, BoundInterpolatedStringExpression), diagnostics)

ElseIf expr.IsLateBound() Then

Select Case expr.GetLateBoundAccessKind()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
End Select
Next

Return New BoundInterpolatedStringExpression(syntax, contentBuilder.ToImmutableAndFree(), binder:=Me, type:=GetSpecialType(SpecialType.System_String, syntax, diagnostics))
Return New BoundInterpolatedStringExpression(syntax, contentBuilder.ToImmutableAndFree(), constructionOpt:=Nothing, type:=GetSpecialType(SpecialType.System_String, syntax, diagnostics))

End Function

Expand Down Expand Up @@ -72,5 +72,174 @@ Namespace Microsoft.CodeAnalysis.VisualBasic

End Function

Private Function BindUnconvertedInterpolatedStringToString(node As BoundInterpolatedStringExpression, diagnostics As BindingDiagnosticBag) As BoundExpression

Debug.Assert(node.Type.SpecialType = SpecialType.System_String)

' We lower an interpolated string into an invocation of String.Format or System.Runtime.CompilerServices.FormattableStringFactory.Create.
' For example, we translate the expression:
'
' $"Jenny, don't change your number: {phoneNumber:###-####}."
'
' into
'
' String.Format("Jenny, don't change your number: {0:###-####}.", phoneNumber)
'
' TODO: A number of optimizations would be beneficial in the generated code.
'
' (1) If there is no width or format, and the argument is a value type, call .ToString()
' on it directly so that we avoid the boxing overhead.
'
' (2) For the built-in types, we can use .ToString(string format) for some format strings.
' Detect those cases that can be handled that way and take advantage of them.
If node.IsEmpty OrElse Not node.HasInterpolations Then
Return node
End If

Return node.Update(node.Contents,
constructionOpt:=TryInvokeInterpolatedStringFactory(node,
factoryType:=node.Type,
factoryMethodName:="Format",
targetType:=node.Type,
diagnostics),
node.Type)
End Function

Private Function BindUnconvertedInterpolatedStringToFormattable(syntax As SyntaxNode, node As BoundInterpolatedStringExpression, targetType As TypeSymbol, diagnostics As BindingDiagnosticBag) As BoundExpression

Debug.Assert(targetType.Equals(Compilation.GetWellKnownType(WellKnownType.System_FormattableString)) OrElse
targetType.Equals(Compilation.GetWellKnownType(WellKnownType.System_IFormattable)))

' We lower an interpolated string into an invocation of System.Runtime.CompilerServices.FormattableStringFactory.Create.
' For example, we translate the expression:
'
' $"Jenny, don't change your number: {phoneNumber:###-####}."
'
' into
'
' FormattableStringFactory.Create("Jenny, don't change your number: {0:###-####}.", phoneNumber)
'
' TODO: A number of optimizations would be beneficial in the generated code.
'
' (1) If there is no width or format, and the argument is a value type, call .ToString()
' on it directly so that we avoid the boxing overhead.
'
' (2) For the built-in types, we can use .ToString(string format) for some format strings.
' Detect those cases that can be handled that way and take advantage of them.

Return node.Update(node.Contents,
constructionOpt:=TryInvokeInterpolatedStringFactory(node,
factoryType:=GetWellKnownType(WellKnownType.System_Runtime_CompilerServices_FormattableStringFactory, syntax, diagnostics),
factoryMethodName:="Create",
targetType,
diagnostics),
node.Type)
End Function

Private Function TryInvokeInterpolatedStringFactory(node As BoundInterpolatedStringExpression, factoryType As TypeSymbol, factoryMethodName As String, targetType As TypeSymbol, diagnostics As BindingDiagnosticBag) As BoundExpression

Dim hasErrors As Boolean = False

If factoryType.IsErrorType() OrElse
node.HasErrors OrElse
node.Contents.OfType(Of BoundInterpolation)().Any(
Function(interpolation) interpolation.Expression.HasErrors OrElse
(interpolation.AlignmentOpt IsNot Nothing AndAlso
Not (interpolation.AlignmentOpt.IsConstant AndAlso interpolation.AlignmentOpt.ConstantValueOpt.IsIntegral))) Then
Return Nothing
End If

Dim lookup = LookupResult.GetInstance()
Dim useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics)

LookupMember(lookup, factoryType, factoryMethodName, 0, LookupOptions.MustNotBeInstance Or LookupOptions.MethodsOnly Or LookupOptions.AllMethodsOfAnyArity, useSiteInfo)

diagnostics.Add(node, useSiteInfo)

If lookup.Kind = LookupResultKind.Inaccessible Then
hasErrors = True
ElseIf Not lookup.IsGood Then
lookup.Free()
GoTo Report_ERR_InterpolatedStringFactoryError
End If

Dim methodGroup = New BoundMethodGroup(node.Syntax, Nothing, lookup.Symbols.ToDowncastedImmutable(Of MethodSymbol), lookup.Kind, Nothing, QualificationKind.QualifiedViaTypeName).MakeCompilerGenerated()
lookup.Free()

Dim formatStringBuilderHandle = PooledStringBuilder.GetInstance()
Dim arguments = ArrayBuilder(Of BoundExpression).GetInstance()
Dim interpolationOrdinal = -1

arguments.Add(Nothing) ' Placeholder for format string.

For Each item In node.Contents

Select Case item.Kind

Case BoundKind.Literal

formatStringBuilderHandle.Builder.Append(DirectCast(item, BoundLiteral).Value.StringValue)

Case BoundKind.Interpolation

interpolationOrdinal += 1

Dim interpolation = DirectCast(item, BoundInterpolation)

With formatStringBuilderHandle.Builder

.Append("{"c)

.Append(interpolationOrdinal.ToString(Globalization.CultureInfo.InvariantCulture))

If interpolation.AlignmentOpt IsNot Nothing Then
Debug.Assert(interpolation.AlignmentOpt.IsConstant AndAlso interpolation.AlignmentOpt.ConstantValueOpt.IsIntegral)
.Append(","c)
.Append(interpolation.AlignmentOpt.ConstantValueOpt.Int64Value.ToString(Globalization.CultureInfo.InvariantCulture))
End If

If interpolation.FormatStringOpt IsNot Nothing Then
.Append(":")
.Append(interpolation.FormatStringOpt.Value.StringValue)
End If

.Append("}"c)
End With

arguments.Add(interpolation.Expression)

Case Else
Throw ExceptionUtilities.Unreachable()
End Select

Next

arguments(0) = CreateStringLiteral(node.Syntax, formatStringBuilderHandle.ToStringAndFree(), compilerGenerated:=True, diagnostics)

Dim result As BoundExpression = MakeRValue(BindInvocationExpression(node.Syntax,
node.Syntax,
TypeCharacter.None,
methodGroup,
arguments.ToImmutableAndFree(),
Nothing,
diagnostics,
callerInfoOpt:=Nothing,
forceExpandedForm:=True), diagnostics).MakeCompilerGenerated()

If Not result.Type.Equals(targetType) Then
result = ApplyImplicitConversion(node.Syntax, targetType, result, diagnostics).MakeCompilerGenerated()
End If

If hasErrors OrElse result.HasErrors Then
GoTo Report_ERR_InterpolatedStringFactoryError
End If

Return result

Report_ERR_InterpolatedStringFactoryError:
ReportDiagnostic(diagnostics, node.Syntax, ErrorFactory.ErrorInfo(ERRID.ERR_InterpolatedStringFactoryError, factoryType.Name, factoryMethodName))
Return Nothing
End Function

End Class
End Namespace
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
End Function

Private Function CreateStringLiteral(
syntax As VisualBasicSyntaxNode,
syntax As SyntaxNode,
str As String,
compilerGenerated As Boolean,
diagnostics As BindingDiagnosticBag,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1927,7 +1927,7 @@
<!-- Type is required for this node type; may not be null -->
<Field Name="Type" Type="TypeSymbol" Override="true" Null="disallow"/>
<Field Name="Contents" Type="ImmutableArray(Of BoundNode)"/>
<Field Name="Binder" Type="Binder" Null="disallow" />
<Field Name="ConstructionOpt" Type="BoundExpression" Null="allow" SkipInVisitor="true"/>
</Node>

<!-- There is no BoundInterpolatedStringText because we use BoundLiteral of type string for this purpose. -->
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,10 @@ lSelect:
Me._diagnostics.Add(New VBDiagnostic(ErrorFactory.ErrorInfo(code), node.Syntax.GetLocation()))
End Sub

Public Overrides Function VisitInterpolatedStringExpression(node As BoundInterpolatedStringExpression) As BoundNode
Visit(node.ConstructionOpt)
Return MyBase.VisitInterpolatedStringExpression(node)
End Function
End Class

End Namespace
Loading

0 comments on commit 88c8ad9

Please sign in to comment.