Skip to content

Commit

Permalink
Add support for all parenthesized binary additions of interpolated st…
Browse files Browse the repository at this point in the history
…rings (#56333)

Implements the decision from dotnet/csharplang#5106. Test plan at #51499.
  • Loading branch information
333fred authored Sep 22, 2021
1 parent 1c56266 commit b071cda
Show file tree
Hide file tree
Showing 9 changed files with 941 additions and 222 deletions.
153 changes: 51 additions & 102 deletions src/Compilers/CSharp/Portable/Binder/Binder_InterpolatedString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
Expand Down Expand Up @@ -187,7 +188,7 @@ bool tryBindAsHandlerType([NotNullWhen(true)] out BoundInterpolatedString? resul
{
result = null;

if (InExpressionTree || !ValidateInterpolatedStringParts(unconvertedInterpolatedString))
if (InExpressionTree || !InterpolatedStringPartsAreValidInDefaultHandler(unconvertedInterpolatedString))
{
return false;
}
Expand All @@ -204,7 +205,7 @@ bool tryBindAsHandlerType([NotNullWhen(true)] out BoundInterpolatedString? resul
}
}

private static bool ValidateInterpolatedStringParts(BoundUnconvertedInterpolatedString unconvertedInterpolatedString)
private static bool InterpolatedStringPartsAreValidInDefaultHandler(BoundUnconvertedInterpolatedString unconvertedInterpolatedString)
=> !unconvertedInterpolatedString.Parts.ContainsAwaitExpression()
&& unconvertedInterpolatedString.Parts.All(p => p is not BoundStringInsert { Value.Type.TypeKind: TypeKind.Dynamic });

Expand All @@ -230,71 +231,41 @@ private bool TryBindUnconvertedBinaryOperatorToDefaultInterpolatedStringHandler(
return false;
}

bool isConstant = true;
var stack = ArrayBuilder<BoundBinaryOperator>.GetInstance();
var partsArrayBuilder = ArrayBuilder<ImmutableArray<BoundExpression>>.GetInstance();
int partsCount = 0;

BoundBinaryOperator? current = binaryOperator;

while (current != null)
// The constant value is folded as part of creating the unconverted operator. If there is a constant value, then the top-level binary operator
// will have one.
if (binaryOperator.ConstantValue is not null)
{
Debug.Assert(current.IsUnconvertedInterpolatedStringAddition);
stack.Push(current);
isConstant = isConstant && current.Right.ConstantValue is not null;
var rightInterpolatedString = (BoundUnconvertedInterpolatedString)current.Right;

if (!ValidateInterpolatedStringParts(rightInterpolatedString))
{
// Exception to case 3. Delegate to standard binding.
stack.Free();
partsArrayBuilder.Free();
return false;
}

partsCount += rightInterpolatedString.Parts.Length;
partsArrayBuilder.Add(rightInterpolatedString.Parts);

switch (current.Left)
{
case BoundBinaryOperator leftOperator:
current = leftOperator;
continue;
case BoundUnconvertedInterpolatedString interpolatedString:
isConstant = isConstant && interpolatedString.ConstantValue is not null;
// This is case 1. Let the standard machinery handle it
return false;
}
var partsArrayBuilder = ArrayBuilder<ImmutableArray<BoundExpression>>.GetInstance();

if (!ValidateInterpolatedStringParts(interpolatedString))
if (!binaryOperator.VisitBinaryOperatorInterpolatedString(
partsArrayBuilder,
static (BoundUnconvertedInterpolatedString unconvertedInterpolatedString, ArrayBuilder<ImmutableArray<BoundExpression>> partsArrayBuilder) =>
{
if (!InterpolatedStringPartsAreValidInDefaultHandler(unconvertedInterpolatedString))
{
// Exception to case 3. Delegate to standard binding.
stack.Free();
partsArrayBuilder.Free();
return false;
}
partsCount += interpolatedString.Parts.Length;
partsArrayBuilder.Add(interpolatedString.Parts);
current = null;
break;
default:
throw ExceptionUtilities.UnexpectedValue(current.Left.Kind);
}
partsArrayBuilder.Add(unconvertedInterpolatedString.Parts);
return true;
}))
{
partsArrayBuilder.Free();
return false;
}

Debug.Assert(partsArrayBuilder.Count == stack.Count + 1);
Debug.Assert(partsArrayBuilder.Count >= 2);

if (isConstant ||
(partsCount <= 4 && partsArrayBuilder.All(static parts => AllInterpolatedStringPartsAreStrings(parts))))
if (partsArrayBuilder.Count <= 4 && partsArrayBuilder.All(static parts => AllInterpolatedStringPartsAreStrings(parts)))
{
// This is case 1 and 2. Let the standard machinery handle it
stack.Free();
// This is case 2. Let the standard machinery handle it
partsArrayBuilder.Free();
return false;
}

// Parts were added to the array from right to left, but lexical order is left to right.
partsArrayBuilder.ReverseContents();

// Case 3. Bind as handler.
var (appendCalls, data) = BindUnconvertedInterpolatedPartsToHandlerType(
binaryOperator.Syntax,
Expand All @@ -306,51 +277,48 @@ private bool TryBindUnconvertedBinaryOperatorToDefaultInterpolatedStringHandler(
additionalConstructorRefKinds: default);

// Now that the parts have been bound, reconstruct the binary operators.
convertedBinaryOperator = UpdateBinaryOperatorWithInterpolatedContents(stack, appendCalls, data, binaryOperator.Syntax, diagnostics);
stack.Free();
convertedBinaryOperator = UpdateBinaryOperatorWithInterpolatedContents(binaryOperator, appendCalls, data, binaryOperator.Syntax, diagnostics);
return true;
}

private BoundBinaryOperator UpdateBinaryOperatorWithInterpolatedContents(ArrayBuilder<BoundBinaryOperator> stack, ImmutableArray<ImmutableArray<BoundExpression>> appendCalls, InterpolatedStringHandlerData data, SyntaxNode rootSyntax, BindingDiagnosticBag diagnostics)
private BoundBinaryOperator UpdateBinaryOperatorWithInterpolatedContents(BoundBinaryOperator originalOperator, ImmutableArray<ImmutableArray<BoundExpression>> appendCalls, InterpolatedStringHandlerData data, SyntaxNode rootSyntax, BindingDiagnosticBag diagnostics)
{
Debug.Assert(appendCalls.Length == stack.Count + 1);
var @string = GetSpecialType(SpecialType.System_String, diagnostics, rootSyntax);

var bottomOperator = stack.Pop();
var result = createBinaryOperator(bottomOperator, createInterpolation(bottomOperator.Left, appendCalls[0]), rightIndex: 1);
Func<BoundUnconvertedInterpolatedString, int, (ImmutableArray<ImmutableArray<BoundExpression>>, TypeSymbol), BoundExpression> interpolationFactory =
createInterpolation;
Func<BoundBinaryOperator, BoundExpression, BoundExpression, (ImmutableArray<ImmutableArray<BoundExpression>>, TypeSymbol), BoundExpression> binaryOperatorFactory =
createBinaryOperator;

var rewritten = (BoundBinaryOperator)originalOperator.RewriteInterpolatedStringAddition((appendCalls, @string), interpolationFactory, binaryOperatorFactory);

for (int i = 2; i < appendCalls.Length; i++)
return rewritten.Update(BoundBinaryOperator.UncommonData.InterpolatedStringHandlerAddition(data));

static BoundInterpolatedString createInterpolation(BoundUnconvertedInterpolatedString expression, int i, (ImmutableArray<ImmutableArray<BoundExpression>> AppendCalls, TypeSymbol _) arg)
{
result = createBinaryOperator(stack.Pop(), result, rightIndex: i);
Debug.Assert(arg.AppendCalls.Length > i);
return new BoundInterpolatedString(
expression.Syntax,
interpolationData: null,
arg.AppendCalls[i],
expression.ConstantValue,
expression.Type,
expression.HasErrors);
}

return result.Update(BoundBinaryOperator.UncommonData.InterpolatedStringHandlerAddition(data));

BoundBinaryOperator createBinaryOperator(BoundBinaryOperator original, BoundExpression left, int rightIndex)
static BoundBinaryOperator createBinaryOperator(BoundBinaryOperator original, BoundExpression left, BoundExpression right, (ImmutableArray<ImmutableArray<BoundExpression>> _, TypeSymbol @string) arg)
=> new BoundBinaryOperator(
original.Syntax,
BinaryOperatorKind.StringConcatenation,
left,
createInterpolation(original.Right, appendCalls[rightIndex]),
right,
original.ConstantValue,
methodOpt: null,
constrainedToTypeOpt: null,
LookupResultKind.Viable,
originalUserDefinedOperatorsOpt: default,
@string,
arg.@string,
original.HasErrors);

static BoundInterpolatedString createInterpolation(BoundExpression expression, ImmutableArray<BoundExpression> parts)
{
Debug.Assert(expression is BoundUnconvertedInterpolatedString);
return new BoundInterpolatedString(
expression.Syntax,
interpolationData: null,
parts,
expression.ConstantValue,
expression.Type,
expression.HasErrors);
}
}

private BoundExpression BindUnconvertedInterpolatedExpressionToHandlerType(
Expand Down Expand Up @@ -408,32 +376,14 @@ private BoundBinaryOperator BindUnconvertedBinaryOperatorToInterpolatedStringHan
{
Debug.Assert(binaryOperator.IsUnconvertedInterpolatedStringAddition);

var stack = ArrayBuilder<BoundBinaryOperator>.GetInstance();
var partsArrayBuilder = ArrayBuilder<ImmutableArray<BoundExpression>>.GetInstance();

BoundBinaryOperator? current = binaryOperator;

while (current != null)
{
stack.Push(current);
partsArrayBuilder.Add(((BoundUnconvertedInterpolatedString)current.Right).Parts);

if (current.Left is BoundBinaryOperator next)
binaryOperator.VisitBinaryOperatorInterpolatedString(partsArrayBuilder,
static (BoundUnconvertedInterpolatedString unconvertedInterpolatedString, ArrayBuilder<ImmutableArray<BoundExpression>> partsArrayBuilder) =>
{
current = next;
}
else
{
partsArrayBuilder.Add(((BoundUnconvertedInterpolatedString)current.Left).Parts);
current = null;
}
}

// Parts are added in right to left order, but lexical is left to right.
partsArrayBuilder.ReverseContents();

Debug.Assert(partsArrayBuilder.Count == stack.Count + 1);
Debug.Assert(partsArrayBuilder.Count >= 2);
partsArrayBuilder.Add(unconvertedInterpolatedString.Parts);
return true;
});

var (appendCalls, data) = BindUnconvertedInterpolatedPartsToHandlerType(
binaryOperator.Syntax,
Expand All @@ -444,8 +394,7 @@ private BoundBinaryOperator BindUnconvertedBinaryOperatorToInterpolatedStringHan
additionalConstructorArguments,
additionalConstructorRefKinds);

var result = UpdateBinaryOperatorWithInterpolatedContents(stack, appendCalls, data, binaryOperator.Syntax, diagnostics);
stack.Free();
var result = UpdateBinaryOperatorWithInterpolatedContents(binaryOperator, appendCalls, data, binaryOperator.Syntax, diagnostics);
return result;
}

Expand Down
47 changes: 28 additions & 19 deletions src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ private BoundExpression BindSimpleBinaryOperator(BinaryExpressionSyntax node, Bi
if (leaveUnconvertedIfInterpolatedString
&& kind == BinaryOperatorKind.Addition
&& left is BoundUnconvertedInterpolatedString or BoundBinaryOperator { IsUnconvertedInterpolatedStringAddition: true }
&& right is BoundUnconvertedInterpolatedString)
&& right is BoundUnconvertedInterpolatedString or BoundBinaryOperator { IsUnconvertedInterpolatedStringAddition: true })
{
Debug.Assert(right.Type.SpecialType == SpecialType.System_String);
var stringConstant = FoldBinaryOperator(node, BinaryOperatorKind.StringConcatenation, left, right, right.Type, diagnostics);
Expand Down Expand Up @@ -712,29 +712,38 @@ private BoundExpression RebindSimpleBinaryOperatorAsConverted(BoundBinaryOperato
return convertedBinaryOperator;
}

var stack = ArrayBuilder<BoundBinaryOperator>.GetInstance();
BoundBinaryOperator? current = unconvertedBinaryOperator;
var result = doRebind(diagnostics, unconvertedBinaryOperator);
return result;

while (current != null)
BoundExpression doRebind(BindingDiagnosticBag diagnostics, BoundBinaryOperator? current)
{
Debug.Assert(current.IsUnconvertedInterpolatedStringAddition);
stack.Push(current);
current = current.Left as BoundBinaryOperator;
}
var stack = ArrayBuilder<BoundBinaryOperator>.GetInstance();
while (current != null)
{
Debug.Assert(current.IsUnconvertedInterpolatedStringAddition);
stack.Push(current);
current = current.Left as BoundBinaryOperator;
}

Debug.Assert(stack.Count > 0 && stack.Peek().Left is BoundUnconvertedInterpolatedString);
Debug.Assert(stack.Count > 0 && stack.Peek().Left is BoundUnconvertedInterpolatedString);

BoundExpression? left = null;
while (stack.TryPop(out current))
{
Debug.Assert(current.Right is BoundUnconvertedInterpolatedString);
left = BindSimpleBinaryOperator((BinaryExpressionSyntax)current.Syntax, diagnostics, left ?? current.Left, current.Right, leaveUnconvertedIfInterpolatedString: false);
}
BoundExpression? left = null;
while (stack.TryPop(out current))
{
var right = current.Right switch
{
BoundUnconvertedInterpolatedString s => s,
BoundBinaryOperator b => doRebind(diagnostics, b),
_ => throw ExceptionUtilities.UnexpectedValue(current.Right.Kind)
};
left = BindSimpleBinaryOperator((BinaryExpressionSyntax)current.Syntax, diagnostics, left ?? current.Left, right, leaveUnconvertedIfInterpolatedString: false);
}

Debug.Assert(left != null);
Debug.Assert(stack.Count == 0);
stack.Free();
return left;
Debug.Assert(left != null);
Debug.Assert(stack.Count == 0);
stack.Free();
return left;
}
}
#nullable disable

Expand Down
Loading

0 comments on commit b071cda

Please sign in to comment.