Skip to content

Commit

Permalink
Initial rough prototype of ??= (coalesce-assignment).
Browse files Browse the repository at this point in the history
  • Loading branch information
ashmind committed Apr 28, 2016
1 parent 8670489 commit cf5ee99
Show file tree
Hide file tree
Showing 28 changed files with 252 additions and 29 deletions.
3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,9 @@ private BoundExpression BindExpressionInternal(ExpressionSyntax node, Diagnostic
case SyntaxKind.SubtractAssignmentExpression:
return BindCompoundAssignment((AssignmentExpressionSyntax)node, diagnostics);

case SyntaxKind.CoalesceAssignmentExpression:
return BindNullCoalescingAssignment((AssignmentExpressionSyntax)node, diagnostics);

case SyntaxKind.AliasQualifiedName:
case SyntaxKind.PredefinedType:
return this.BindNamespaceOrType(node, diagnostics);
Expand Down
60 changes: 45 additions & 15 deletions src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2438,6 +2438,7 @@ private static BindValueKind GetBinaryAssignmentKind(SyntaxKind kind)
case SyntaxKind.OrAssignmentExpression:
case SyntaxKind.RightShiftAssignmentExpression:
case SyntaxKind.SubtractAssignmentExpression:
case SyntaxKind.CoalesceAssignmentExpression:
return BindValueKind.CompoundAssignment;
default:
return BindValueKind.RValue;
Expand Down Expand Up @@ -3128,22 +3129,41 @@ internal static ConstantValue GetAsOperatorConstantResult(TypeSymbol operandType
return null;
}

private BoundExpression GenerateNullCoalescingBadBinaryOpsError(BinaryExpressionSyntax node, BoundExpression leftOperand, BoundExpression rightOperand, Conversion leftConversion, DiagnosticBag diagnostics)
private BoundExpression GenerateNullCoalescingBadBinaryOpsError(bool isAssignment, ExpressionSyntax node, BoundExpression leftOperand, BoundExpression rightOperand, Conversion leftConversion, DiagnosticBag diagnostics)
{
Error(diagnostics, ErrorCode.ERR_BadBinaryOps, node, SyntaxFacts.GetText(node.OperatorToken.Kind()), leftOperand.Display, rightOperand.Display);
return new BoundNullCoalescingOperator(node, leftOperand, rightOperand,
leftConversion, CreateErrorType(), hasErrors: true);
var operatorKind = isAssignment ? SyntaxKind.QuestionQuestionEqualsToken : SyntaxKind.QuestionQuestionToken;
Error(diagnostics, ErrorCode.ERR_BadBinaryOps, node, SyntaxFacts.GetText(operatorKind), leftOperand.Display, rightOperand.Display);
if (isAssignment)
{
return new BoundNullCoalescingAssignmentOperator(node, leftOperand, rightOperand, leftConversion, CreateErrorType(), hasErrors: true);
}
else
{
return new BoundNullCoalescingOperator(node, leftOperand, rightOperand, leftConversion, CreateErrorType(), hasErrors: true);
}
}

private BoundExpression BindNullCoalescingOperator(BinaryExpressionSyntax node, DiagnosticBag diagnostics)
private BoundExpression GenerateNullCoalescingAssignmentOrOperator(bool isAssignment, ExpressionSyntax node, BoundExpression leftOperand, BoundExpression rightOperand, Conversion leftConversion, TypeSymbol type, bool hasErrors = false)
{
var leftOperand = BindValue(node.Left, diagnostics, BindValueKind.RValue);
var rightOperand = BindValue(node.Right, diagnostics, BindValueKind.RValue);
if (isAssignment)
{
return new BoundNullCoalescingAssignmentOperator(node, leftOperand, rightOperand, leftConversion, type, hasErrors);
}
else
{
return new BoundNullCoalescingOperator(node, leftOperand, rightOperand, Conversion.NoConversion, type, hasErrors);
}
}

private BoundExpression BindNullCoalescingOperatorOrAssignment(bool isAssignment, ExpressionSyntax node, ExpressionSyntax left, ExpressionSyntax right, DiagnosticBag diagnostics)
{
var leftOperand = BindValue(left, diagnostics, isAssignment ? BindValueKind.CompoundAssignment : BindValueKind.RValue);
var rightOperand = BindValue(right, diagnostics, BindValueKind.RValue);

// If either operand is bad, bail out preventing more cascading errors
if (leftOperand.HasAnyErrors || rightOperand.HasAnyErrors)
{
return new BoundNullCoalescingOperator(node, leftOperand, rightOperand,
return GenerateNullCoalescingAssignmentOrOperator(isAssignment, node, leftOperand, rightOperand,
Conversion.NoConversion, CreateErrorType(), hasErrors: true);
}

Expand Down Expand Up @@ -3172,14 +3192,14 @@ private BoundExpression BindNullCoalescingOperator(BinaryExpressionSyntax node,

if (leftOperand.Kind == BoundKind.UnboundLambda || leftOperand.Kind == BoundKind.MethodGroup)
{
return GenerateNullCoalescingBadBinaryOpsError(node, leftOperand, rightOperand, Conversion.NoConversion, diagnostics);
return GenerateNullCoalescingBadBinaryOpsError(isAssignment, node, leftOperand, rightOperand, Conversion.NoConversion, diagnostics);
}

// SPEC: Otherwise, if A exists and is not a nullable type or a reference type, a compile-time error occurs.

if ((object)optLeftType != null && !optLeftType.IsReferenceType && !isLeftNullable)
{
return GenerateNullCoalescingBadBinaryOpsError(node, leftOperand, rightOperand, Conversion.NoConversion, diagnostics);
return GenerateNullCoalescingBadBinaryOpsError(isAssignment, node, leftOperand, rightOperand, Conversion.NoConversion, diagnostics);
}

// SPEC: If b is a dynamic expression, the result is dynamic. At runtime, a is first
Expand All @@ -3193,7 +3213,7 @@ private BoundExpression BindNullCoalescingOperator(BinaryExpressionSyntax node,
{
var leftConversion = Conversions.ClassifyConversionFromExpression(leftOperand, GetSpecialType(SpecialType.System_Object, diagnostics, node), ref useSiteDiagnostics);
diagnostics.Add(node, useSiteDiagnostics);
return new BoundNullCoalescingOperator(node, leftOperand, rightOperand,
return GenerateNullCoalescingAssignmentOrOperator(isAssignment, node, leftOperand, rightOperand,
leftConversion, optRightType);
}

Expand All @@ -3210,7 +3230,7 @@ private BoundExpression BindNullCoalescingOperator(BinaryExpressionSyntax node,
var leftConversion = Conversions.ClassifyConversionFromExpression(leftOperand, optLeftType0, ref useSiteDiagnostics);
diagnostics.Add(node, useSiteDiagnostics);
var convertedRightOperand = CreateConversion(rightOperand, rightConversion, optLeftType0, diagnostics);
return new BoundNullCoalescingOperator(node, leftOperand, convertedRightOperand,
return GenerateNullCoalescingAssignmentOrOperator(isAssignment, node, leftOperand, convertedRightOperand,
leftConversion, optLeftType0);
}
}
Expand All @@ -3227,7 +3247,7 @@ private BoundExpression BindNullCoalescingOperator(BinaryExpressionSyntax node,
var convertedRightOperand = CreateConversion(rightOperand, rightConversion, optLeftType, diagnostics);
var leftConversion = Conversion.Identity;
diagnostics.Add(node, useSiteDiagnostics);
return new BoundNullCoalescingOperator(node, leftOperand, convertedRightOperand,
return GenerateNullCoalescingAssignmentOrOperator(isAssignment, node, leftOperand, convertedRightOperand,
leftConversion, optLeftType);
}
}
Expand Down Expand Up @@ -3298,13 +3318,23 @@ private BoundExpression BindNullCoalescingOperator(BinaryExpressionSyntax node,
}

diagnostics.Add(node, useSiteDiagnostics);
return new BoundNullCoalescingOperator(node, leftOperand, rightOperand, leftConversion, optRightType);
return GenerateNullCoalescingAssignmentOrOperator(isAssignment, node, leftOperand, rightOperand, leftConversion, optRightType);
}
}

// SPEC: Otherwise, a and b are incompatible, and a compile-time error occurs.
diagnostics.Add(node, useSiteDiagnostics);
return GenerateNullCoalescingBadBinaryOpsError(node, leftOperand, rightOperand, Conversion.NoConversion, diagnostics);
return GenerateNullCoalescingBadBinaryOpsError(isAssignment, node, leftOperand, rightOperand, Conversion.NoConversion, diagnostics);
}

private BoundExpression BindNullCoalescingOperator(BinaryExpressionSyntax node, DiagnosticBag diagnostics)
{
return BindNullCoalescingOperatorOrAssignment(false, node, node.Left, node.Right, diagnostics);
}

private BoundExpression BindNullCoalescingAssignment(AssignmentExpressionSyntax node, DiagnosticBag diagnostics)
{
return BindNullCoalescingOperatorOrAssignment(true, node, node.Left, node.Right, diagnostics);
}

/// <remarks>
Expand Down
6 changes: 6 additions & 0 deletions src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,12 @@
<Field Name="LeftConversion" Type="Conversion"/>
</Node>

<Node Name="BoundNullCoalescingAssignmentOperator" Base="BoundExpression">
<Field Name="Left" Type="BoundExpression"/>
<Field Name="Right" Type="BoundExpression"/>
<Field Name="LeftConversion" Type="Conversion"/>
</Node>

<Node Name="BoundConditionalOperator" Base="BoundExpression">
<!-- Non-null type is required for this node kind -->
<Field Name="Type" Type="TypeSymbol" Override="true" Null="disallow"/>
Expand Down
2 changes: 2 additions & 0 deletions src/Compilers/CSharp/Portable/BoundTree/BoundTreeVisitors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public virtual R Visit(BoundNode node, A arg)
return VisitAssignmentOperator(node as BoundAssignmentOperator, arg);
case BoundKind.NullCoalescingOperator:
return VisitNullCoalescingOperator(node as BoundNullCoalescingOperator, arg);
case BoundKind.NullCoalescingAssignmentOperator:
return VisitNullCoalescingAssignmentOperator(node as BoundNullCoalescingAssignmentOperator, arg);
case BoundKind.ConditionalOperator:
return VisitConditionalOperator(node as BoundConditionalOperator, arg);
case BoundKind.ArrayAccess:
Expand Down
18 changes: 18 additions & 0 deletions src/Compilers/CSharp/Portable/BoundTree/Expression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1180,6 +1180,24 @@ public override TResult Accept<TArgument, TResult>(OperationVisitor<TArgument, T
}
}

// TODO (coalesce-assignment)
internal partial class BoundNullCoalescingAssignmentOperator
{
protected override OperationKind ExpressionKind
{
get { throw new NotImplementedException(); }
}

public override void Accept(OperationVisitor visitor)
{
throw new NotImplementedException();
}

public override TResult Accept<TArgument, TResult>(OperationVisitor<TArgument, TResult> visitor, TArgument argument) {
throw new NotImplementedException();
}
}

internal partial class BoundAwaitExpression : IAwaitExpression
{
IOperation IAwaitExpression.AwaitedValue => this.Expression;
Expand Down
3 changes: 2 additions & 1 deletion src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@
<Compile Include="Lowering\LocalRewriter\LocalRewriter_Block.cs" />
<Compile Include="Lowering\LocalRewriter\LocalRewriter_BreakStatement.cs" />
<Compile Include="Lowering\LocalRewriter\LocalRewriter_Call.cs" />
<Compile Include="Lowering\LocalRewriter\LocalRewriter_NullCoalescingAssignmentOperator.cs" />
<Compile Include="Lowering\LocalRewriter\LocalRewriter_CompoundAssignmentOperator.cs" />
<Compile Include="Lowering\LocalRewriter\LocalRewriter_ConditionalAccess.cs" />
<Compile Include="Lowering\LocalRewriter\LocalRewriter_ConditionalOperator.cs" />
Expand Down Expand Up @@ -889,4 +890,4 @@
<ImportGroup Label="Targets">
<Import Project="..\..\..\..\build\Targets\VSL.Imports.targets" />
</ImportGroup>
</Project>
</Project>
7 changes: 7 additions & 0 deletions src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1565,6 +1565,13 @@ public override BoundNode VisitCompoundAssignmentOperator(BoundCompoundAssignmen
return null;
}

public override BoundNode VisitNullCoalescingAssignmentOperator(BoundNullCoalescingAssignmentOperator node)
{
base.VisitNullCoalescingAssignmentOperator(node);
Assign(node.Left, node.Right);
return null;
}

public override BoundNode VisitAddressOfOperator(BoundAddressOfOperator node)
{
BoundExpression operand = node.Operand;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1483,27 +1483,38 @@ public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node)
}

public override BoundNode VisitCompoundAssignmentOperator(BoundCompoundAssignmentOperator node)
{
return VisitCompositeAssignmentInternal(node, node.Left, node.Right);
}

public override BoundNode VisitNullCoalescingAssignmentOperator(BoundNullCoalescingAssignmentOperator node)
{
// TODO (coalesce-assignment): should we mark whole thing as unreachable/noop if the right part is constant null?
return VisitCompositeAssignmentInternal(node, node.Left, node.Right);
}

private BoundNode VisitCompositeAssignmentInternal(BoundExpression node, BoundExpression left, BoundExpression right)
{
// TODO: should events be handled specially too?
if (RegularPropertyAccess(node.Left))
if (RegularPropertyAccess(left))
{
var left = (BoundPropertyAccess)node.Left;
var property = left.PropertySymbol;
var leftPropertyAccess = (BoundPropertyAccess)left;
var property = leftPropertyAccess.PropertySymbol;
var readMethod = property.GetOwnOrInheritedGetMethod() ?? property.SetMethod;
var writeMethod = property.GetOwnOrInheritedSetMethod() ?? property.GetMethod;
Debug.Assert(node.HasAnyErrors || (object)readMethod != (object)writeMethod);
VisitReceiverBeforeCall(left.ReceiverOpt, readMethod);
VisitReceiverBeforeCall(leftPropertyAccess.ReceiverOpt, readMethod);
if (_trackExceptions) NotePossibleException(node);
VisitReceiverAfterCall(left.ReceiverOpt, readMethod);
VisitRvalue(node.Right);
PropertySetter(node, left.ReceiverOpt, writeMethod);
VisitReceiverAfterCall(leftPropertyAccess.ReceiverOpt, readMethod);
VisitRvalue(right);
PropertySetter(node, leftPropertyAccess.ReceiverOpt, writeMethod);
if (_trackExceptions) NotePossibleException(node);
VisitReceiverAfterCall(left.ReceiverOpt, writeMethod);
VisitReceiverAfterCall(leftPropertyAccess.ReceiverOpt, writeMethod);
}
else
{
VisitRvalue(node.Left);
VisitRvalue(node.Right);
VisitRvalue(left);
VisitRvalue(right);
if (_trackExceptions && node.HasExpressionSymbols()) NotePossibleException(node);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ private BoundExpression VisitUnusedExpression(BoundExpression expression)
case BoundKind.CompoundAssignmentOperator:
return VisitCompoundAssignmentOperator((BoundCompoundAssignmentOperator)expression, used: false);

case BoundKind.NullCoalescingAssignmentOperator:
return VisitNullCoalescingAssignmentOperator((BoundNullCoalescingAssignmentOperator)expression, used: false);

case BoundKind.Call:
if (_allowOmissionOfConditionalCalls)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp
{
internal sealed partial class LocalRewriter
{
public override BoundNode VisitNullCoalescingAssignmentOperator(BoundNullCoalescingAssignmentOperator node)
{
return VisitNullCoalescingAssignmentOperator(node, true);
}

private BoundExpression VisitNullCoalescingAssignmentOperator(BoundNullCoalescingAssignmentOperator node, bool used)
{
BoundExpression rewrittenRight = VisitExpression(node.Right);

var temps = ArrayBuilder<LocalSymbol>.GetInstance();
var stores = ArrayBuilder<BoundExpression>.GetInstance();

// This will be filled in with the LHS that uses temporaries to prevent
// double-evaluation of side effects.
var transformedLeft = TransformCompoundAssignmentLHS(node.Left, stores, temps, false);

CSharpSyntaxNode syntax = node.Syntax;

// OK now we have all the temporaries.
// What we want to generate is:
//
// xlhs = xlhs ?? xrhs;
//
// TODO (coalesce-assignment): this is of course a bit lazy, what we might want is
// (xlhs != null ? xlhs : (xlhs = MakeConversion(xhrs)));

var readLeft = MakeRValue(transformedLeft);
var coalesce = MakeNullCoalescingOperator(syntax, readLeft, rewrittenRight, node.LeftConversion, node.Type);
var assignment = MakeAssignmentOperator(syntax, transformedLeft, coalesce, node.Left.Type, used: used, isChecked: false, isCompoundAssignment: true);

// OK, at this point we have:
//
// * temps evaluating and storing portions of the LHS that must be evaluated only once.
// * the "transformed" left hand side, rebuilt to use temps where necessary
// * the assignment "xlhs = ((LEFT)xlhs ?? rhs)"
//
// Notice that we have recursively rewritten the bound nodes that are things stored in
// the temps, and by calling the "Make" methods we have rewritten the conversions and
// assignments too, if necessary.

BoundExpression result = (temps.Count == 0 && stores.Count == 0) ?
assignment :
new BoundSequence(
syntax,
temps.ToImmutable(),
stores.ToImmutable(),
assignment,
assignment.Type);

temps.Free();
stores.Free();
return result;
}
}
}
3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/Parser/LanguageParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8172,6 +8172,7 @@ internal static bool IsRightAssociative(SyntaxKind op)
case SyntaxKind.LeftShiftAssignmentExpression:
case SyntaxKind.RightShiftAssignmentExpression:
case SyntaxKind.CoalesceExpression:
case SyntaxKind.CoalesceAssignmentExpression:
return true;
default:
return false;
Expand All @@ -8193,6 +8194,7 @@ private static uint GetPrecedence(SyntaxKind op)
case SyntaxKind.OrAssignmentExpression:
case SyntaxKind.LeftShiftAssignmentExpression:
case SyntaxKind.RightShiftAssignmentExpression:
case SyntaxKind.CoalesceAssignmentExpression:
return 1;
case SyntaxKind.CoalesceExpression:
return 2;
Expand Down Expand Up @@ -9272,6 +9274,7 @@ private static bool CanFollowCast(SyntaxKind kind)
case SyntaxKind.BarEqualsToken:
case SyntaxKind.LessThanLessThanEqualsToken:
case SyntaxKind.GreaterThanGreaterThanEqualsToken:
case SyntaxKind.QuestionQuestionEqualsToken:
case SyntaxKind.QuestionToken:
case SyntaxKind.ColonToken:
case SyntaxKind.BarBarToken:
Expand Down
Loading

0 comments on commit cf5ee99

Please sign in to comment.