Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VB: Do not use overload resolution while lowering interpolated strings #75449

Merged
merged 2 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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