Skip to content

Commit

Permalink
Deconstruction-assignment: expression type should be void (#11594)
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv committed May 27, 2016
1 parent 6cb6df1 commit f041726
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 34 deletions.
8 changes: 4 additions & 4 deletions src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1772,8 +1772,8 @@ private BoundExpression BindDeconstructWithTuple(AssignmentExpressionSyntax node
// figure out the pairwise conversions
var assignments = checkedVariables.SelectAsArray((variable, index, types) => MakeAssignmentInfo(variable, types[index], node, diagnostics), tupleTypes);

TypeSymbol lastType = tupleTypes.Last();
return new BoundDeconstructionAssignmentOperator(node, checkedVariables, typedRHS, deconstructMemberOpt: null, assignments: assignments, type: lastType);
TypeSymbol voidType = GetSpecialType(SpecialType.System_Void, diagnostics, node);
return new BoundDeconstructionAssignmentOperator(node, checkedVariables, typedRHS, deconstructMemberOpt: null, assignments: assignments, type: voidType);
}

private BoundExpression BindDeconstructWithDeconstruct(AssignmentExpressionSyntax node, DiagnosticBag diagnostics, BoundExpression boundRHS, ImmutableArray<BoundExpression> checkedVariables)
Expand All @@ -1798,8 +1798,8 @@ private BoundExpression BindDeconstructWithDeconstruct(AssignmentExpressionSynta
var deconstructParameters = deconstructMethod.Parameters;
var assignments = checkedVariables.SelectAsArray((variable, index, parameters) => MakeAssignmentInfo(variable, parameters[index].Type, node, diagnostics), deconstructParameters);

TypeSymbol lastType = deconstructParameters.Last().Type;
return new BoundDeconstructionAssignmentOperator(node, checkedVariables, boundRHS, deconstructMethod, assignments, lastType);
TypeSymbol voidType = GetSpecialType(SpecialType.System_Void, diagnostics, node);
return new BoundDeconstructionAssignmentOperator(node, checkedVariables, boundRHS, deconstructMethod, assignments, voidType);
}

/// <summary>
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,11 @@
<Field Name="Assignments" Type="ImmutableArray&lt;BoundDeconstructionAssignmentOperator.AssignmentInfo&gt;" Null="NotApplicable" SkipInVisitor="true"/>
</Node>

<Node Name="BoundVoid" Base="BoundExpression">
<!-- Non-null type is required for this node kind, but it will always be the void type -->
<Field Name="Type" Type="TypeSymbol" Override="true" Null="disallow"/>
</Node>

<Node Name="BoundNullCoalescingOperator" Base="BoundExpression">
<Field Name="LeftOperand" Type="BoundExpression"/>
<Field Name="RightOperand" Type="BoundExpression"/>
Expand Down
15 changes: 15 additions & 0 deletions src/Compilers/CSharp/Portable/BoundTree/Expression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,21 @@ public override TResult Accept<TArgument, TResult>(OperationVisitor<TArgument, T
}
}

internal sealed partial class BoundVoid : BoundExpression
{
protected override OperationKind ExpressionKind => OperationKind.None;

public override void Accept(OperationVisitor visitor)
{
visitor.VisitNoneOperation(this);
}

public override TResult Accept<TArgument, TResult>(OperationVisitor<TArgument, TResult> visitor, TArgument argument)
{
return visitor.VisitNoneOperation(this, argument);
}
}

internal partial class BoundCompoundAssignmentOperator : ICompoundAssignmentExpression
{
BinaryOperationKind ICompoundAssignmentExpression.BinaryOperationKind => Expression.DeriveBinaryOperationKind(this.Operator.Kind);
Expand Down
4 changes: 4 additions & 0 deletions src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,10 @@ private void EmitExpressionCore(BoundExpression expression, bool used)
EmitPseudoVariableValue((BoundPseudoVariable)expression, used);
break;

case BoundKind.Void:
Debug.Assert(!used);
break;

default:
// Code gen should not be invoked if there are errors.
Debug.Assert(expression.Kind != BoundKind.BadExpression);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstruct
stores.Add(assignment);
}

stores.Add(new BoundVoid(node.Syntax, node.Type));
var result = _factory.Sequence(temps.ToImmutable(), stores.ToArray());

temps.Free();
Expand Down
95 changes: 65 additions & 30 deletions src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen
public class CodeGenDeconstructTests : CSharpTestBase
{
[Fact]
public void Deconstruct()
public void SimpleAssign()
{
string source = @"
class C
Expand Down Expand Up @@ -140,7 +140,7 @@ public void Deconstruct(out int a)
}

[Fact]
public void DeconstructWithLeftHandSideErrors()
public void AssignmentWithLeftHandSideErrors()
{
string source = @"
class C
Expand Down Expand Up @@ -240,7 +240,7 @@ public int Deconstruct(out int a, out string b)
}

[Fact]
public void DeconstructDataFlow()
public void AssignmentDataFlow()
{
string source = @"
class C
Expand Down Expand Up @@ -342,7 +342,7 @@ public void Deconstruct(out int a, out string b, out string c)
comp.VerifyDiagnostics();
}

[Fact(Skip = "PROTOTYPE(tuples)")]
[Fact]
public void Dynamic()
{
string source = @"
Expand All @@ -354,8 +354,8 @@ class C
static void Main()
{
C c = new C();
(c.Dynamic1, c.Dynamic2) = new C();
System.Console.WriteLine(c.Dynamic1 + "" "" + c.Dyanmic2);
(c.Dynamic1, c.Dynamic2) = c;
System.Console.WriteLine(c.Dynamic1 + "" "" + c.Dynamic2);
}
public void Deconstruct(out int a, out dynamic b)
Expand All @@ -366,7 +366,7 @@ public void Deconstruct(out int a, out dynamic b)
}
";

var comp = CompileAndVerify(source, expectedOutput: "1 hello", additionalRefs: new[] { SystemCoreRef }, parseOptions: TestOptions.Regular.WithTuplesFeature());
var comp = CompileAndVerify(source, expectedOutput: "1 hello", additionalRefs: new[] { SystemCoreRef, CSharpRef }, parseOptions: TestOptions.Regular.WithTuplesFeature());
comp.VerifyDiagnostics();
}

Expand Down Expand Up @@ -402,7 +402,7 @@ public void Deconstruct(out int a, out string b, out string c)
comp.VerifyDiagnostics();
}

[Fact(Skip = "PROTOTYPE(tuples)")]
[Fact]
public void DifferentVariableRefKinds()
{
string source = @"
Expand Down Expand Up @@ -495,7 +495,7 @@ public void Deconstruct(out string b, out string c)
// expect a console output
}

[Fact(Skip = "PROTOTYPE(tuples)")]
[Fact]
public void ExpressionType()
{
string source = @"
Expand All @@ -505,7 +505,6 @@ static void Main()
{
int x, y;
var type = ((x, y) = new C()).GetType();
System.Console.WriteLine(type);
}
public void Deconstruct(out int a, out int b)
Expand All @@ -516,7 +515,11 @@ public void Deconstruct(out int a, out int b)
";

var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature());
comp.VerifyDiagnostics(); // expect an error
comp.VerifyDiagnostics(
// (7,38): error CS0023: Operator '.' cannot be applied to operand of type 'void'
// var type = ((x, y) = new C()).GetType();
Diagnostic(ErrorCode.ERR_BadUnaryOp, ".").WithArguments(".", "void").WithLocation(7, 38)
);
}

[Fact]
Expand Down Expand Up @@ -705,7 +708,7 @@ void Deconstruct(out int i, out int j)
//Diagnostic(ErrorCode.WRN_UnassignedInternalField, "i").WithArguments("C.i", "0").WithLocation(4, 16)
}

[Fact(Skip = "PROTOTYPE(tuples)")]
[Fact]
[CompilerTrait(CompilerFeature.RefLocalsReturns)]
public void RefReturningMethodFlow()
{
Expand Down Expand Up @@ -741,16 +744,16 @@ public static implicit operator C(int x)
}
";

var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature());
comp.VerifyDiagnostics();


var expected =
@"";
@"M (previous i is C)
M (previous i is C)
getP
Deconstruct
conversion
conversion";

// Should not crash!
var comp2 = CompileAndVerify(source, expectedOutput: expected, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature());
comp2.VerifyDiagnostics();
var comp = CompileAndVerify(source, expectedOutput: expected, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature());
comp.VerifyDiagnostics();
}

[Fact]
Expand Down Expand Up @@ -884,7 +887,7 @@ static void M() { }
}

[Fact]
public void DeconstructTuple()
public void AssigningTuple()
{
string source = @"
class C
Expand All @@ -905,7 +908,7 @@ static void Main()
}

[Fact(Skip = "PROTOTYPE(tuples)")]
public void DeconstructTuple2()
public void AssigningTuple2()
{
string source = @"
class C
Expand All @@ -931,7 +934,7 @@ static System.ValueTuple<int, string> M()
}

[Fact]
public void DeconstructLongTuple()
public void AssigningLongTuple()
{
string source = @"
class C
Expand Down Expand Up @@ -1021,7 +1024,7 @@ .locals init (long V_0, //x
}

[Fact]
public void DeconstructLongTupleWithNames()
public void AssigningLongTupleWithNames()
{
string source = @"
class C
Expand All @@ -1041,8 +1044,8 @@ static void Main()
comp.VerifyDiagnostics();
}

[Fact(Skip = "PROTOTYPE(tuples)")]
public void DeconstructLongTuple2()
[Fact]
public void AssigningLongTuple2()
{
string source = @"
class C
Expand All @@ -1058,13 +1061,12 @@ static void Main()
}
";

// issue with return type
var comp = CompileAndVerify(source, expectedOutput: "4 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature());
comp.VerifyDiagnostics();
}

[Fact]
public void DeconstructTypelessTuple()
public void AssigningTypelessTuple()
{
string source = @"
class C
Expand Down Expand Up @@ -1133,7 +1135,7 @@ static void Main()
}

[Fact]
public void DeconstructIntoProperties()
public void AssigningIntoProperties()
{
string source = @"
class C
Expand Down Expand Up @@ -1162,7 +1164,7 @@ public void Deconstruct(out int a, out string b)
}

[Fact]
public void DeconstructTupleIntoProperties()
public void AssigningTupleIntoProperties()
{
string source = @"
class C
Expand Down Expand Up @@ -1308,5 +1310,38 @@ static void Main()
var comp = CompileAndVerify(source, expectedOutput: "(1, 1) 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature());
comp.VerifyDiagnostics();
}

[Fact]
public void AssignmentTypeIsVoid()
{
string source = @"
class C
{
static void Main()
{
int x, y;
((x, y) = new C()).ToString();
var z = ((x, y) = new C());
}
public void Deconstruct(out int a, out int b)
{
a = 1;
b = 2;
}
}
";
var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature());
comp.VerifyDiagnostics(
// (8,27): error CS0023: Operator '.' cannot be applied to operand of type 'void'
// ((x, y) = new C()).ToString();
Diagnostic(ErrorCode.ERR_BadUnaryOp, ".").WithArguments(".", "void").WithLocation(8, 27),
// (10,13): error CS0815: Cannot assign void to an implicitly-typed variable
// var z = ((x, y) = new C());
Diagnostic(ErrorCode.ERR_ImplicitlyTypedVariableAssignedBadValue, "z = ((x, y) = new C())").WithArguments("void").WithLocation(10, 13)
);
}
}
}

0 comments on commit f041726

Please sign in to comment.