Skip to content

Commit

Permalink
Report constraint errors on deconstruction of typeless tuple
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv authored and jcouv committed Sep 27, 2017
1 parent 952792a commit a135d99
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 42 deletions.
27 changes: 16 additions & 11 deletions src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
Expand Down Expand Up @@ -95,13 +94,18 @@ internal BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Dia
DeconstructionVariable locals = BindDeconstructionVariables(left, diagnostics, ref declaration, ref expression);
Debug.Assert(locals.HasNestedVariables);

BoundExpression boundRight = rightPlaceholder ?? BindValue(right, diagnostics, BindValueKind.RValue);
boundRight = FixTupleLiteral(locals.NestedVariables, boundRight, deconstruction, diagnostics);
var deconstructionDiagnostics = new DiagnosticBag();
BoundExpression boundRight = rightPlaceholder ?? BindValue(right, deconstructionDiagnostics, BindValueKind.RValue);
if (!deconstructionDiagnostics.HasAnyErrors())
{
boundRight = FixTupleLiteral(locals.NestedVariables, boundRight, deconstruction, deconstructionDiagnostics);
}

bool resultIsUsed = resultIsUsedOverride || IsDeconstructionResultUsed(left);
var assignment = BindDeconstructionAssignment(deconstruction, left, boundRight, locals.NestedVariables, resultIsUsed, diagnostics);
var assignment = BindDeconstructionAssignment(deconstruction, left, boundRight, locals.NestedVariables, resultIsUsed, deconstructionDiagnostics);
DeconstructionVariable.FreeDeconstructionVariables(locals.NestedVariables);

diagnostics.AddRange(deconstructionDiagnostics);
return assignment;
}

Expand Down Expand Up @@ -143,7 +147,7 @@ internal BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Dia

FailRemainingInferencesAndSetValEscape(checkedVariables, diagnostics, rightEscape);

var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: hasErrors || !resultIsUsed);
var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: diagnostics.HasAnyErrors() || !resultIsUsed);
TypeSymbol returnType = hasErrors ? CreateErrorType() : lhsTuple.Type;

uint leftEscape = GetBroadestValEscape(lhsTuple, this.LocalScopeDepth);
Expand Down Expand Up @@ -453,6 +457,7 @@ private static TypeSymbol MakeMergedTupleType(ArrayBuilder<DeconstructionVariabl
int rightLength = rhsLiteral.Arguments.Length;

var typesBuilder = ArrayBuilder<TypeSymbol>.GetInstance(leftLength);
var locationsBuilder = ArrayBuilder<Location>.GetInstance(leftLength);
for (int i = 0; i < rightLength; i++)
{
BoundExpression element = rhsLiteral.Arguments[i];
Expand Down Expand Up @@ -493,29 +498,29 @@ private static TypeSymbol MakeMergedTupleType(ArrayBuilder<DeconstructionVariabl
}

typesBuilder.Add(mergedType);
locationsBuilder.Add(element.Syntax.Location);
}

if (typesBuilder.Any(t => t == null))
{
typesBuilder.Free();
locationsBuilder.Free();
return null;
}

// The tuple created here is not identical to the one created by
// DeconstructionVariablesAsTuple. It represents a smaller
// tree of types used for figuring out natural types in tuple literal.
// Therefore, we do not check constraints here as it would report errors
// that are already reported later. DeconstructionVariablesAsTuple
// constructs the final tuple type and checks constraints.
return TupleTypeSymbol.Create(
locationOpt: null,
elementTypes: typesBuilder.ToImmutableAndFree(),
elementLocations: default(ImmutableArray<Location>),
elementLocations: locationsBuilder.ToImmutableAndFree(),
elementNames: default(ImmutableArray<string>),
compilation: compilation,
diagnostics: diagnostics,
shouldCheckConstraints: false,
errorPositions: default(ImmutableArray<bool>));
shouldCheckConstraints: true,
errorPositions: default(ImmutableArray<bool>),
syntax: syntax);
}

private BoundTupleLiteral DeconstructionVariablesAsTuple(CSharpSyntaxNode syntax, ArrayBuilder<DeconstructionVariable> variables,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4596,10 +4596,7 @@ static void Main()
Diagnostic(ErrorCode.ERR_NoVoidHere, "void").WithLocation(5, 17),
// (5,31): error CS8210: A tuple may not contain a value of type 'void'.
// (int x, void y) = (1, Main());
Diagnostic(ErrorCode.ERR_VoidInTuple, "Main()").WithLocation(5, 31),
// (5,17): error CS0029: Cannot implicitly convert type 'void' to 'void'
// (int x, void y) = (1, Main());
Diagnostic(ErrorCode.ERR_NoImplicitConv, "void y").WithArguments("void", "void").WithLocation(5, 17)
Diagnostic(ErrorCode.ERR_VoidInTuple, "Main()").WithLocation(5, 31)
);
var main = comp.GetMember<MethodSymbol>("C.Main");
var tree = comp.SyntaxTrees[0];
Expand Down
94 changes: 71 additions & 23 deletions src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2447,7 +2447,7 @@ public class C
public void M(ref Span<int> global)
{
Span<int> local = stackalloc int[10];
(M(), M()) = local;
(M(), M()) = local; // error
}
public static void Main() => throw null;
public ref Span<int> M() => throw null;
Expand All @@ -2459,7 +2459,7 @@ public static class Extensions
";
CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics(
// (9,22): error CS8352: Cannot use local 'local' in this context because it may expose referenced variables outside of their declaration scope
// (M(), M()) = local;
// (M(), M()) = local; // error
Diagnostic(ErrorCode.ERR_EscapeLocal, "local").WithArguments("local").WithLocation(9, 22),
// warning CS1685: The predefined type 'ExtensionAttribute' is defined in multiple assemblies in the global alias; using definition from 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
Diagnostic(ErrorCode.WRN_MultiplePredefTypes).WithArguments("System.Runtime.CompilerServices.ExtensionAttribute", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089").WithLocation(1, 1)
Expand Down Expand Up @@ -2509,7 +2509,7 @@ public void M(ref Span<int> global)
{
Span<int> local = stackalloc int[10];
(global, global) = global;
(global, global) = local;
(global, global) = local; // error
}
public static void Main() => throw null;
}
Expand All @@ -2520,7 +2520,7 @@ public static class Extensions
";
CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics(
// (10,28): error CS8352: Cannot use local 'local' in this context because it may expose referenced variables outside of their declaration scope
// (global, global) = local;
// (global, global) = local; // error
Diagnostic(ErrorCode.ERR_EscapeLocal, "local").WithArguments("local").WithLocation(10, 28),
// warning CS1685: The predefined type 'ExtensionAttribute' is defined in multiple assemblies in the global alias; using definition from 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
Diagnostic(ErrorCode.WRN_MultiplePredefTypes).WithArguments("System.Runtime.CompilerServices.ExtensionAttribute", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089").WithLocation(1, 1)
Expand All @@ -2537,7 +2537,7 @@ public class C
{
public void M(ref Span<int> global)
{
var t = ((global, global) = global);
var t = ((global, global) = global); // error
}
public static void Main() => throw null;
}
Expand All @@ -2561,10 +2561,10 @@ public ValueTuple(T1 item1, T2 item2)
}";
CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics(
// (8,19): error CS0306: The type 'Span<int>' may not be used as a type argument
// var t = ((global, global) = global);
// var t = ((global, global) = global); // error
Diagnostic(ErrorCode.ERR_BadTypeArgument, "global").WithArguments("System.Span<int>").WithLocation(8, 19),
// (8,27): error CS0306: The type 'Span<int>' may not be used as a type argument
// var t = ((global, global) = global);
// var t = ((global, global) = global); // error
Diagnostic(ErrorCode.ERR_BadTypeArgument, "global").WithArguments("System.Span<int>").WithLocation(8, 27),
// warning CS1685: The predefined type 'ExtensionAttribute' is defined in multiple assemblies in the global alias; using definition from 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
Diagnostic(ErrorCode.WRN_MultiplePredefTypes).WithArguments("System.Runtime.CompilerServices.ExtensionAttribute", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089").WithLocation(1, 1)
Expand All @@ -2582,7 +2582,9 @@ public class C
public void M(ref Span<int> global)
{
Span<int> local = stackalloc int[10];
(global, global) = (local, local);
string s;
(global, global) = (local, local); // error 1
(global, s) = (local, """"); // error 2
}
public static void Main() => throw null;
}
Expand All @@ -2602,12 +2604,58 @@ public ValueTuple(T1 item1, T2 item2)
}
";
CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics(
// (9,29): error CS0306: The type 'Span<int>' may not be used as a type argument
// (global, global) = (local, local);
Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span<int>").WithLocation(9, 29),
// (9,36): error CS0306: The type 'Span<int>' may not be used as a type argument
// (global, global) = (local, local);
Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span<int>").WithLocation(9, 36)
// (10,29): error CS0306: The type 'Span<int>' may not be used as a type argument
// (global, global) = (local, local); // error 1
Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span<int>").WithLocation(10, 29),
// (10,36): error CS0306: The type 'Span<int>' may not be used as a type argument
// (global, global) = (local, local); // error 1
Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span<int>").WithLocation(10, 36),
// (11,24): error CS0306: The type 'Span<int>' may not be used as a type argument
// (global, s) = (local, ""); // error 2
Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span<int>").WithLocation(11, 24)
);
}

[Fact]
public void DeconstructionAssignmentOfTypelessTuple()
{
var text = @"
using System;
public class C
{
public void M(ref Span<int> global)
{
Span<int> local = stackalloc int[10];
Span<int> local2 = stackalloc int[10];
string s;
(global, s) = (local, null); // error 1
(local2, s) = (local, null); // error 2
}
public static void Main() => throw null;
}
namespace System
{
public struct ValueTuple<T1, T2>
{
public T1 Item1;
public T2 Item2;
public ValueTuple(T1 item1, T2 item2)
{
this.Item1 = item1;
this.Item2 = item2;
}
}
}
";
CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics(
// (11,24): error CS0306: The type 'Span<int>' may not be used as a type argument
// (global, s) = (local, null); // error 1
Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span<int>").WithLocation(11, 24),
// (12,24): error CS0306: The type 'Span<int>' may not be used as a type argument
// (local2, s) = (local, null); // error 2
Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span<int>").WithLocation(12, 24)
);
}

Expand All @@ -2622,7 +2670,7 @@ public class C
public void M(ref Span<int> global)
{
Span<int> local = stackalloc int[10];
(global, global) = (local, local);
(global, global) = (local, local); // error
}
public static void Main() => throw null;
}
Expand All @@ -2644,10 +2692,10 @@ public ValueTuple(T1 item1, T2 item2)
";
CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics(
// (9,29): error CS0306: The type 'Span<int>' may not be used as a type argument
// (global, global) = (local, local);
// (global, global) = (local, local); // error
Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span<int>").WithLocation(9, 29),
// (9,36): error CS0306: The type 'Span<int>' may not be used as a type argument
// (global, global) = (local, local);
// (global, global) = (local, local); // error
Diagnostic(ErrorCode.ERR_BadTypeArgument, "local").WithArguments("System.Span<int>").WithLocation(9, 36)
);
}
Expand Down Expand Up @@ -2698,8 +2746,8 @@ public void M(ref Span<int> global)
{
Span<int> local = stackalloc int[10];
var (local1, local2) = local;
global = local1;
global = local2;
global = local1; // error 1
global = local2; // error 2
var (local3, local4) = global;
global = local3;
Expand All @@ -2714,10 +2762,10 @@ public static class Extensions
";
CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics(
// (10,18): error CS8352: Cannot use local 'local1' in this context because it may expose referenced variables outside of their declaration scope
// global = local1;
// global = local1; // error 1
Diagnostic(ErrorCode.ERR_EscapeLocal, "local1").WithArguments("local1").WithLocation(10, 18),
// (11,18): error CS8352: Cannot use local 'local2' in this context because it may expose referenced variables outside of their declaration scope
// global = local2;
// global = local2; // error 2
Diagnostic(ErrorCode.ERR_EscapeLocal, "local2").WithArguments("local2").WithLocation(11, 18),
// warning CS1685: The predefined type 'ExtensionAttribute' is defined in multiple assemblies in the global alias; using definition from 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
Diagnostic(ErrorCode.WRN_MultiplePredefTypes).WithArguments("System.Runtime.CompilerServices.ExtensionAttribute", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089").WithLocation(1, 1)
Expand All @@ -2737,7 +2785,7 @@ public void M(ref S global)
S localCollection = stackalloc int[10];
foreach (var local in localCollection)
{
global = local;
global = local; // error
}
}
public static void Main() => throw null;
Expand All @@ -2753,7 +2801,7 @@ public void M(ref S global)
";
CreateCompilationWithMscorlibAndSpan(text).VerifyDiagnostics(
// (11,22): error CS8352: Cannot use local 'local' in this context because it may expose referenced variables outside of their declaration scope
// global = local;
// global = local; // error
Diagnostic(ErrorCode.ERR_EscapeLocal, "local").WithArguments("local").WithLocation(11, 22)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4953,24 +4953,68 @@ static void Main()
}

[Fact]
public void TupleLiteralElement004()
public void TupleLiteralElement004_WithoutValueTuple()
{
var source =
@"
class C
{
static void Main()
{
(short X, string Y) = (Alice: 1, Bob: null);
}
}
";

using System;
var compilation = CreateStandardCompilation(source);
compilation.VerifyDiagnostics(
// (6,31): error CS8179: Predefined type 'System.ValueTuple`2' is not defined or imported, or is declared in multiple referenced assemblies
// (short X, string Y) = (Alice: 1, Bob: null);
Diagnostic(ErrorCode.ERR_PredefinedValueTupleTypeNotFound, "(Alice: 1, Bob: null)").WithArguments("System.ValueTuple`2").WithLocation(6, 31)
);
var tree = compilation.SyntaxTrees[0];
var decl = (ArgumentSyntax)tree.GetCompilationUnitRoot().DescendantNodes().Last(n => n.IsKind(SyntaxKind.Argument));
var model = compilation.GetSemanticModel(tree);
Assert.Null(model.GetDeclaredSymbol(decl));
}

[Fact]
public void TupleLiteralElement004()
{
var source =
@"
class C
{
static void Main()
{
static void Main()
{
(short X, string Y) = (Alice: 1, Bob: null);
}
}
namespace System
{
public struct ValueTuple<T1, T2>
{
public T1 Item1;
public T2 Item2;
public ValueTuple(T1 item1, T2 item2)
{
this.Item1 = item1;
this.Item2 = item2;
}
}
}
";

var compilation = CreateStandardCompilation(source);
compilation.VerifyDiagnostics(
// (6,32): warning CS8123: The tuple element name 'Alice' is ignored because a different name or no name is specified by the target type '(short, string)'.
// (short X, string Y) = (Alice: 1, Bob: null);
Diagnostic(ErrorCode.WRN_TupleLiteralNameMismatch, "Alice: 1").WithArguments("Alice", "(short, string)").WithLocation(6, 32),
// (6,42): warning CS8123: The tuple element name 'Bob' is ignored because a different name or no name is specified by the target type '(short, string)'.
// (short X, string Y) = (Alice: 1, Bob: null);
Diagnostic(ErrorCode.WRN_TupleLiteralNameMismatch, "Bob: null").WithArguments("Bob", "(short, string)").WithLocation(6, 42)
);
var tree = compilation.SyntaxTrees[0];
var decl = (ArgumentSyntax)tree.GetCompilationUnitRoot().DescendantNodes().Last(n => n.IsKind(SyntaxKind.Argument));
var model = compilation.GetSemanticModel(tree);
Expand Down

0 comments on commit a135d99

Please sign in to comment.