提交 004b2816 编写于 作者: N Neal Gafter 提交者: GitHub

In a syntactic position where an lvalue is expected, we reserve `var (...)` (#13636)

* In a syntactic position where an lvalue is expected, we reserve `var (...)`
for possible future extension (e.g. a deconstruction), no matter what `...` is.
This specifically applies to
  - The left-hand-side of an assignment. The only syntactically valid form is as a deconstruction declaration statement; other forms are some kind of error. `var(1) = 2;`
  - Similarly for compound assignments, including ++ and --.
  - In any expression, following `out` or `ref`. These should not bind (successfully) as an invocation.
    - In an argument position, like `M(out var (1))`
    - In a "ref expression", for example `ref int x = ref var(1);` or `return ref var(1);`.
上级 6cab3509
......@@ -7027,15 +7027,6 @@ internal class CSharpResources {
}
}
/// <summary>
/// Looks up a localized string similar to Deconstruction is not supported for an &apos;out&apos; argument..
/// </summary>
internal static string ERR_OutVarDeconstructionIsNotSupported {
get {
return ResourceManager.GetString("ERR_OutVarDeconstructionIsNotSupported", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &apos;{0}&apos; cannot define overloaded methods that differ only on ref and out.
/// </summary>
......@@ -9331,6 +9322,15 @@ internal class CSharpResources {
}
}
/// <summary>
/// Looks up a localized string similar to The syntax &apos;var (...)&apos; as an lvalue is reserved..
/// </summary>
internal static string ERR_VarInvocationLvalueReserved {
get {
return ResourceManager.GetString("ERR_VarInvocationLvalueReserved", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &apos;{0}&apos;: virtual or abstract members cannot be private.
/// </summary>
......
......@@ -4947,8 +4947,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_InvalidInstrumentationKind" xml:space="preserve">
<value>Invalid instrumentation kind: {0}</value>
</data>
<data name="ERR_OutVarDeconstructionIsNotSupported" xml:space="preserve">
<value>Deconstruction is not supported for an 'out' argument.</value>
<data name="ERR_VarInvocationLvalueReserved" xml:space="preserve">
<value>The syntax 'var (...)' as an lvalue is reserved.</value>
</data>
<data name="ERR_ExpressionVariableInConstructorOrFieldInitializer" xml:space="preserve">
<value>Out variable and pattern variable declarations are not allowed within constructor initializers, field initializers, or property initializers.</value>
......
......@@ -1432,7 +1432,7 @@ internal enum ErrorCode
ERR_ImplicitlyTypedOutVariableUsedInTheSameArgumentList = 8196,
ERR_TypeInferenceFailedForImplicitlyTypedOutVariable = 8197,
ERR_ExpressionTreeContainsOutVariable = 8198,
ERR_OutVarDeconstructionIsNotSupported = 8199,
ERR_VarInvocationLvalueReserved = 8199,
ERR_ExpressionVariableInConstructorOrFieldInitializer = 8200,
#endregion diagnostics for out var
}
......
......@@ -3390,6 +3390,7 @@ private ExpressionSyntax ParsePossibleRefExpression()
var expression = this.ParseExpressionCore();
if (refKeyword != default(SyntaxToken))
{
expression = CheckValidLvalue(expression);
expression = _syntaxFactory.RefExpression(refKeyword, expression);
}
......@@ -4842,6 +4843,7 @@ private static bool CanReuseVariableDeclarator(CSharp.Syntax.VariableDeclaratorS
var init = this.ParseVariableInitializer(isLocal && !isConst);
if (refKeyword != null)
{
init = CheckValidLvalue(init);
init = _syntaxFactory.RefExpression(refKeyword, init);
}
......@@ -9248,6 +9250,11 @@ private ExpressionSyntax ParseSubExpressionCore(Precedence precedence)
newPrecedence = GetPrecedence(opKind);
var opToken = this.EatToken();
var operand = this.ParseSubExpression(newPrecedence);
if (SyntaxFacts.IsIncrementOrDecrementOperator(opToken.Kind))
{
operand = CheckValidLvalue(operand);
}
leftOperand = _syntaxFactory.PrefixUnaryExpression(opKind, opToken, operand);
}
else if (IsAwaitExpression())
......@@ -9366,6 +9373,7 @@ private ExpressionSyntax ParseSubExpressionCore(Precedence precedence)
opToken = AddTrailingSkippedSyntax(opToken, refToken);
}
leftOperand = CheckValidLvalue(leftOperand);
leftOperand = _syntaxFactory.AssignmentExpression(opKind, leftOperand, opToken, this.ParseSubExpression(newPrecedence));
}
else
......@@ -9564,6 +9572,7 @@ private ExpressionSyntax ParsePostFixExpression(ExpressionSyntax expr)
case SyntaxKind.PlusPlusToken:
case SyntaxKind.MinusMinusToken:
expr = CheckValidLvalue(expr);
expr = _syntaxFactory.PostfixUnaryExpression(SyntaxFacts.GetPostfixUnaryExpression(tk), expr, this.EatToken());
break;
......@@ -9843,14 +9852,9 @@ private ArgumentSyntax ParseArgumentExpression(bool isIndexer)
{
expression = this.ParseSubExpression(Precedence.Expression);
// See if the expression is an invocation that could also be successfully parsed and interpreted
// as a deconstruction target. I.e. something like "var (x, y)" or "var (x, (y, z))".
// We should report an error in this case because we plan to support deconstruction for out arguments
// in the future. We need to ensure that we do not successfully interpret this as an invocation of a
// ref-returning method named var with two, or more arguments.
if (IsDeconstructionCompatibleArgument(refOrOutKeyword, expression))
if (refOrOutKeyword != null)
{
expression = this.AddError(expression, ErrorCode.ERR_OutVarDeconstructionIsNotSupported);
expression = CheckValidLvalue(expression);
}
}
}
......@@ -9858,60 +9862,35 @@ private ArgumentSyntax ParseArgumentExpression(bool isIndexer)
return _syntaxFactory.Argument(nameColon, refOrOutKeyword, expression);
}
private static bool IsDeconstructionCompatibleArgument(SyntaxToken refOrOutKeyword, ExpressionSyntax expression)
private ExpressionSyntax CheckValidLvalue(ExpressionSyntax expression)
{
if (refOrOutKeyword?.Kind == SyntaxKind.OutKeyword &&
expression.Kind == SyntaxKind.InvocationExpression)
return IsDeconstructionCompatibleArgument(expression)
? this.AddError(expression, ErrorCode.ERR_VarInvocationLvalueReserved)
: expression;
}
/// <summary>
/// See if the expression is an invocation of a method named 'var',
/// I.e. something like "var(x, y)" or "var(x, (y, z))" or "var(1)".
/// We report an error when such an invocation is used in a certain syntactic contexts that
/// will require an lvalue because we may elect to support deconstruction
/// in the future. We need to ensure that we do not successfully interpret this as an invocation of a
/// ref-returning method named var.
/// </summary>
private static bool IsDeconstructionCompatibleArgument(ExpressionSyntax expression)
{
if (expression.Kind == SyntaxKind.InvocationExpression)
{
var invocation = (InvocationExpressionSyntax)expression;
ExpressionSyntax invocationTarget = invocation.Expression;
return invocationTarget.Kind == SyntaxKind.IdentifierName &&
((IdentifierNameSyntax)invocationTarget).Identifier.IsVar() &&
invocation.ArgumentList.Arguments.Count > 1 &&
!expression.GetDiagnostics().Contains(info => info.Severity == DiagnosticSeverity.Error) &&
IsDeconstructionCompatibleArgumentList(invocation.ArgumentList.Arguments);
((IdentifierNameSyntax)invocationTarget).Identifier.IsVar();
}
return false;
}
private static bool IsDeconstructionCompatibleArgumentList(SeparatedSyntaxList<ArgumentSyntax> arguments)
{
int count = arguments.Count;
for (int i = 0; i < count; i++)
{
ArgumentSyntax argument = arguments[i];
if (argument.NameColon != null || argument.RefOrOutKeyword != null)
{
return false;
}
switch (argument.Expression.Kind)
{
case SyntaxKind.IdentifierName:
// Identifier is compatible
break;
case SyntaxKind.TupleExpression:
// Tuple is compatible if its argument list is compatible
if (!IsDeconstructionCompatibleArgumentList(((TupleExpressionSyntax)argument.Expression).Arguments))
{
return false;
}
break;
default:
// Nothing else can be compatible.
return false;
}
}
return true;
}
private bool IsPossibleOutVarDeclaration()
{
var tk = this.CurrentToken.Kind;
......
......@@ -414,6 +414,18 @@ public static SyntaxKind GetPostfixUnaryExpression(SyntaxKind token)
}
}
internal static bool IsIncrementOrDecrementOperator(SyntaxKind token)
{
switch (token)
{
case SyntaxKind.PlusPlusToken:
case SyntaxKind.MinusMinusToken:
return true;
default:
return false;
}
}
public static bool IsUnaryOperatorDeclarationToken(SyntaxKind token)
{
return IsPrefixUnaryExpressionOperatorToken(token) ||
......
......@@ -1982,15 +1982,24 @@ static void Main()
var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef });
comp.VerifyDiagnostics(
// (6,9): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// var (int x1, x2) = (1, 2);
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var (int x1, x2)").WithLocation(6, 9),
// (6,14): error CS1525: Invalid expression term 'int'
// var (int x1, x2) = (1, 2);
Diagnostic(ErrorCode.ERR_InvalidExprTerm, "int").WithArguments("int").WithLocation(6, 14),
// (6,18): error CS1003: Syntax error, ',' expected
// var (int x1, x2) = (1, 2);
Diagnostic(ErrorCode.ERR_SyntaxError, "x1").WithArguments(",", "").WithLocation(6, 18),
// (7,9): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// var (var x3, x4) = (1, 2);
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var (var x3, x4)").WithLocation(7, 9),
// (7,18): error CS1003: Syntax error, ',' expected
// var (var x3, x4) = (1, 2);
Diagnostic(ErrorCode.ERR_SyntaxError, "x3").WithArguments(",", "").WithLocation(7, 18),
// (8,9): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// var (x5, var (x6, x7)) = (1, (2, 3));
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var (x5, var (x6, x7))").WithLocation(8, 9),
// (6,18): error CS0103: The name 'x1' does not exist in the current context
// var (int x1, x2) = (1, 2);
Diagnostic(ErrorCode.ERR_NameNotInContext, "x1").WithArguments("x1").WithLocation(6, 18),
......
......@@ -78,9 +78,9 @@ static object Test1(out int x)
var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular);
compilation.VerifyDiagnostics(
// (6,19): error CS8199: Deconstruction is not supported for an 'out' argument.
// (6,19): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// Test1(out var (x1, x2));
Diagnostic(ErrorCode.ERR_OutVarDeconstructionIsNotSupported, "var (x1, x2)").WithLocation(6, 19),
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var (x1, x2)").WithLocation(6, 19),
// (6,24): error CS0103: The name 'x1' does not exist in the current context
// Test1(out var (x1, x2));
Diagnostic(ErrorCode.ERR_NameNotInContext, "x1").WithArguments("x1").WithLocation(6, 24),
......@@ -277,9 +277,9 @@ static object Test1(out int x)
options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular);
compilation.VerifyDiagnostics(
// (11,19): error CS8199: Deconstruction is not supported for an 'out' argument.
// (11,19): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// Test1(out var (x1, (x2, x3)));
Diagnostic(ErrorCode.ERR_OutVarDeconstructionIsNotSupported, "var (x1, (x2, x3))").WithLocation(11, 19),
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var (x1, (x2, x3))").WithLocation(11, 19),
// (4,24): warning CS0649: Field 'Cls.F1' is never assigned to, and will always have its default value 0
// private static int F1;
Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F1").WithArguments("Cls.F1", "0").WithLocation(4, 24)
......@@ -318,12 +318,13 @@ static object Test1(out int x)
var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular);
compilation.VerifyDiagnostics(
// (8,19): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// Test1(out var (x1));
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var (x1)").WithLocation(8, 19),
// (4,24): warning CS0649: Field 'Cls.F1' is never assigned to, and will always have its default value 0
// private static int F1;
Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F1").WithArguments("Cls.F1", "0").WithLocation(4, 24)
);
CompileAndVerify(compilation, expectedOutput: "123");
Assert.False(compilation.SyntaxTrees.Single().GetRoot().DescendantNodes().OfType<DeclarationExpressionSyntax>().Any());
}
......@@ -358,12 +359,13 @@ static object Test1(out int x)
var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular);
compilation.VerifyDiagnostics(
// (9,19): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// Test1(out var (x1, x2: x2));
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var (x1, x2: x2)").WithLocation(9, 19),
// (4,24): warning CS0649: Field 'Cls.F1' is never assigned to, and will always have its default value 0
// private static int F1;
Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F1").WithArguments("Cls.F1", "0").WithLocation(4, 24)
);
CompileAndVerify(compilation, expectedOutput: "123");
Assert.False(compilation.SyntaxTrees.Single().GetRoot().DescendantNodes().OfType<DeclarationExpressionSyntax>().Any());
}
......@@ -398,12 +400,13 @@ static object Test1(out int x)
var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular);
compilation.VerifyDiagnostics(
// (9,19): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// Test1(out var (ref x1, x2));
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var (ref x1, x2)").WithLocation(9, 19),
// (4,24): warning CS0649: Field 'Cls.F1' is never assigned to, and will always have its default value 0
// private static int F1;
Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F1").WithArguments("Cls.F1", "0").WithLocation(4, 24)
);
CompileAndVerify(compilation, expectedOutput: "123");
Assert.False(compilation.SyntaxTrees.Single().GetRoot().DescendantNodes().OfType<DeclarationExpressionSyntax>().Any());
}
......@@ -438,12 +441,13 @@ static object Test1(out int x)
var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular);
compilation.VerifyDiagnostics(
// (9,19): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// Test1(out var (x1, (x2)));
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var (x1, (x2))").WithLocation(9, 19),
// (4,24): warning CS0649: Field 'Cls.F1' is never assigned to, and will always have its default value 0
// private static int F1;
Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F1").WithArguments("Cls.F1", "0").WithLocation(4, 24)
);
CompileAndVerify(compilation, expectedOutput: "123");
Assert.False(compilation.SyntaxTrees.Single().GetRoot().DescendantNodes().OfType<DeclarationExpressionSyntax>().Any());
}
......@@ -478,12 +482,13 @@ static object Test1(out int x)
var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular);
compilation.VerifyDiagnostics(
// (9,19): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// Test1(out var ((x1), x2));
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var ((x1), x2)").WithLocation(9, 19),
// (4,24): warning CS0649: Field 'Cls.F1' is never assigned to, and will always have its default value 0
// private static int F1;
Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F1").WithArguments("Cls.F1", "0").WithLocation(4, 24)
);
CompileAndVerify(compilation, expectedOutput: "123");
Assert.False(compilation.SyntaxTrees.Single().GetRoot().DescendantNodes().OfType<DeclarationExpressionSyntax>().Any());
}
......@@ -511,9 +516,9 @@ static object Test1(out int x)
var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp6));
compilation.VerifyDiagnostics(
// (6,19): error CS8199: Deconstruction is not supported for an 'out' argument.
// (6,19): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// Test1(out var (x1, x2));
Diagnostic(ErrorCode.ERR_OutVarDeconstructionIsNotSupported, "var (x1, x2)").WithLocation(6, 19),
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var (x1, x2)").WithLocation(6, 19),
// (6,24): error CS0103: The name 'x1' does not exist in the current context
// Test1(out var (x1, x2));
Diagnostic(ErrorCode.ERR_NameNotInContext, "x1").WithArguments("x1").WithLocation(6, 24),
......@@ -605,12 +610,13 @@ static object Test1(ref int x)
var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular);
compilation.VerifyDiagnostics(
// (9,19): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// Test1(ref var (x1, x2));
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var (x1, x2)").WithLocation(9, 19),
// (4,24): warning CS0649: Field 'Cls.F1' is never assigned to, and will always have its default value 0
// private static int F1;
Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F1").WithArguments("Cls.F1", "0").WithLocation(4, 24)
);
CompileAndVerify(compilation, expectedOutput: "123");
Assert.False(compilation.SyntaxTrees.Single().GetRoot().DescendantNodes().OfType<DeclarationExpressionSyntax>().Any());
}
......@@ -731,12 +737,13 @@ static object Test1(out int x)
options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular);
compilation.VerifyDiagnostics(
// (11,19): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// Test1(out var (x1, (a: x2, b: x3)));
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var (x1, (a: x2, b: x3))").WithLocation(11, 19),
// (4,24): warning CS0649: Field 'Cls.F1' is never assigned to, and will always have its default value 0
// private static int F1;
Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F1").WithArguments("Cls.F1", "0").WithLocation(4, 24)
);
CompileAndVerify(compilation, expectedOutput: "123");
Assert.False(compilation.SyntaxTrees.Single().GetRoot().DescendantNodes().OfType<DeclarationExpressionSyntax>().Any());
}
......
......@@ -1805,5 +1805,61 @@ struct ValueTuple<T1, T2>
CreateCompilationWithMscorlib(source).VerifyDiagnostics(
);
}
public void NoDeconstructionAsLvalue()
{
var source =
@"
class C
{
void M()
{
var (x, y) = e; // ok, deconstruction declaration
var(x, y); // ok, invocation
int x = var(x, y); // ok, invocation
var(x, y) += e; // error 1
var(x, y)++; // error 2
++var(x, y); // error 3
M(out var(x, y)); // error 4
M(ref var(x, y)); // error 5
return ref var(x, y); // error 6
ref int x = ref var(x, y); // error 7
var (x, 1) = e; // error 8
}
}";
ParseAndValidate(source,
// (9,9): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// var(x, y) += e; // error 1
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var(x, y)").WithLocation(9, 9),
// (10,9): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// var(x, y)++; // error 2
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var(x, y)").WithLocation(10, 9),
// (11,11): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// ++var(x, y); // error 3
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var(x, y)").WithLocation(11, 11),
// (12,15): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// M(out var(x, y)); // error 4
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var(x, y)").WithLocation(12, 15),
// (13,15): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// M(ref var(x, y)); // error 5
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var(x, y)").WithLocation(13, 15),
// (14,20): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// return ref var(x, y); // error 6
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var(x, y)").WithLocation(14, 20),
// (15,25): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// ref int x = ref var(x, y); // error 7
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var(x, y)").WithLocation(15, 25),
// (16,9): error CS8199: The syntax 'var (...)' as an lvalue is reserved.
// var (x, 1) = e; // error 8
Diagnostic(ErrorCode.ERR_VarInvocationLvalueReserved, "var (x, 1)").WithLocation(16, 9)
);
}
public static void ParseAndValidate(string text, params DiagnosticDescription[] expectedErrors)
{
var parsedTree = ParseWithRoundTripCheck(text);
var actualErrors = parsedTree.GetDiagnostics();
actualErrors.Verify(expectedErrors);
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册