提交 0383ce8d 编写于 作者: C CyrusNajmabadi 提交者: GitHub

Improve type argument list parsing in error conditions. (#19467)

* Improve type argument list parsing in error conditions.

* Fix type.

* Remove unused method.

* Improve generic parsing in a couple of additional cases.

* Share more code.

* Inline method.

* Fix logic.

* Add tests.

* Restore test class.

* REmove case, and add test.
上级 001294be
......@@ -5095,11 +5095,39 @@ private ScanTypeArgumentListKind ScanTypeArgumentList(NameOptions options)
return ScanTypeArgumentListKind.DefiniteTypeArgumentList;
}
if (!this.ScanPossibleTypeArgumentList())
// We're in an expression context, and we have a < token. This could be a
// type argument list, or it could just be a relational expression.
//
// Scan just the type argument list portion (i.e. the part from < to > ) to
// see what we think it could be. This will give us one of three possibilities:
//
// result == ScanTypeFlags.NotType.
//
// This is absolutely not a type-argument-list. Just return that result immediately.
//
// result != ScanTypeFlags.NotType && isDefinitelyTypeArgumentList.
//
// This is absolutely a type-argument-list. Just return that result immediately
//
// result != ScanTypeFlags.NotType && !isDefinitelyTypeArgumentList.
//
// This could be a type-argument list, or it could be an expression. Need to see
// what came after the last '>' to find out which it is.
SyntaxToken lastTokenOfList = null;
ScanTypeFlags possibleTypeArgumentFlags = ScanPossibleTypeArgumentList(
ref lastTokenOfList, out bool isDefinitelyTypeArgumentList);
if (possibleTypeArgumentFlags == ScanTypeFlags.NotType)
{
return ScanTypeArgumentListKind.NotTypeArgumentList;
}
if (isDefinitelyTypeArgumentList)
{
return ScanTypeArgumentListKind.DefiniteTypeArgumentList;
}
// Scan for a type argument list. If we think it's a type argument list
// then assume it is unless we see specific tokens following it.
switch (this.CurrentToken.Kind)
......@@ -5164,14 +5192,11 @@ private ScanTypeArgumentListKind ScanTypeArgumentList(NameOptions options)
}
}
private bool ScanPossibleTypeArgumentList()
private ScanTypeFlags ScanPossibleTypeArgumentList(
ref SyntaxToken lastTokenOfList, out bool isDefinitelyTypeArgumentList)
{
SyntaxToken lastTokenOfList = null;
return ScanPossibleTypeArgumentList(ref lastTokenOfList) != ScanTypeFlags.NotType;
}
isDefinitelyTypeArgumentList = false;
private ScanTypeFlags ScanPossibleTypeArgumentList(ref SyntaxToken lastTokenOfList)
{
if (this.CurrentToken.Kind == SyntaxKind.LessThanToken)
{
ScanTypeFlags result = ScanTypeFlags.GenericTypeOrExpression;
......@@ -5199,6 +5224,81 @@ private ScanTypeFlags ScanPossibleTypeArgumentList(ref SyntaxToken lastTokenOfLi
return ScanTypeFlags.NotType;
case ScanTypeFlags.MustBeType:
// We're currently scanning a possible type-argument list. But we're
// not sure if this is actually a type argument list, or is maybe some
// complex relational expression with <'s and >'s. One thing we can
// tell though is that if we have a predefined type (like 'int' or 'string')
// before a comma or > then this is definitely a type argument list. i.e.
// if you have:
//
// var v = ImmutableDictionary<int,
//
// then there's no legal interpretation of this as an expression (since a
// standalone predefined type is not a valid simple term. Contrast that
// with :
//
// var v = ImmutableDictionary<Int32,
//
// Here this might actualy be a relational expression and the comma is meant
// to separate out the variable declarator 'v' from the next variable.
//
// Note: we check if we got 'MustBeType' which triggers for predefined types,
// (int, string, etc.), or array types (Foo[], A<T>[][] etc.), or pointer types
// of things that must be types (int*, void**, etc.).
isDefinitelyTypeArgumentList = DetermineIfDefinitelyTypeArgumentList(isDefinitelyTypeArgumentList);
result = ScanTypeFlags.GenericTypeOrMethod;
break;
// case ScanTypeFlags.TupleType:
// It would be nice if we saw a tuple to state that we definitely had a
// type argument list. However, there are cases where this would not be
// true. For example:
//
// public class C
// {
// public static void Main()
// {
// XX X = default;
// int a = 1, b = 2;
// bool z = X < (a, b), w = false;
// }
// }
//
// struct XX
// {
// public static bool operator <(XX x, (int a, int b) arg) => true;
// public static bool operator >(XX x, (int a, int b) arg) => false;
// }
case ScanTypeFlags.NullableType:
// See above. If we have X<Y?, or X<Y?>, then this is definitely a type argument list.
isDefinitelyTypeArgumentList = DetermineIfDefinitelyTypeArgumentList(isDefinitelyTypeArgumentList);
if (isDefinitelyTypeArgumentList)
{
result = ScanTypeFlags.GenericTypeOrMethod;
}
// Note: we intentionally fall out without setting 'result'.
// Seeing a nullable type (not followed by a , or > ) is not enough
// information for us to determine what this is yet. i.e. the user may have:
//
// X < Y ? Z : W
//
// We'd see a nullable type here, but htis is definitley not a type arg list.
break;
case ScanTypeFlags.GenericTypeOrExpression:
// See above. If we have X<Y<Z>, then this would definitely be a type argument list.
// However, if we have X<Y<Z>> then this might not be type argument list. This could just
// be some sort of expression where we're comparing, and then shifting values.
if (!isDefinitelyTypeArgumentList)
{
isDefinitelyTypeArgumentList = this.CurrentToken.Kind == SyntaxKind.CommaToken;
result = ScanTypeFlags.GenericTypeOrMethod;
}
break;
case ScanTypeFlags.GenericTypeOrMethod:
result = ScanTypeFlags.GenericTypeOrMethod;
break;
......@@ -5219,6 +5319,18 @@ private ScanTypeFlags ScanPossibleTypeArgumentList(ref SyntaxToken lastTokenOfLi
return ScanTypeFlags.NonGenericTypeOrExpression;
}
private bool DetermineIfDefinitelyTypeArgumentList(bool isDefinitelyTypeArgumentList)
{
if (!isDefinitelyTypeArgumentList)
{
isDefinitelyTypeArgumentList =
this.CurrentToken.Kind == SyntaxKind.CommaToken ||
this.CurrentToken.Kind == SyntaxKind.GreaterThanToken;
}
return isDefinitelyTypeArgumentList;
}
// ParseInstantiation: Parses the generic argument/parameter parts of the name.
private void ParseTypeArgumentList(out SyntaxToken open, SeparatedSyntaxListBuilder<TypeSyntax> types, out SyntaxToken close)
{
......@@ -5640,7 +5752,8 @@ private enum ScanTypeFlags
NotType,
/// <summary>
/// Definitely a type name: either a predefined type (int, string, etc.) or an array type name (ending with a bracket).
/// Definitely a type name: either a predefined type (int, string, etc.) or an array
/// type (ending with a [] brackets), or a pointer type (ending with *s).
/// </summary>
MustBeType,
......@@ -5750,7 +5863,7 @@ private ScanTypeFlags ScanNamedTypePart(out SyntaxToken lastTokenOfType)
lastTokenOfType = this.EatToken();
if (this.CurrentToken.Kind == SyntaxKind.LessThanToken)
{
return this.ScanPossibleTypeArgumentList(ref lastTokenOfType);
return this.ScanPossibleTypeArgumentList(ref lastTokenOfType, out _);
}
else
{
......
......@@ -19194,11 +19194,13 @@ public void CS0746ERR_InvalidAnonymousTypeMemberDeclarator_1()
public static int f1 { get { return 1; } }
}
";
DiagnosticsUtils.VerifyErrorsAndGetCompilationWithMscorlib(text,
new ErrorDescription { Code = (int)ErrorCode.ERR_InvalidAnonymousTypeMemberDeclarator, Line = 3, Column = 22 },
new ErrorDescription { Code = (int)ErrorCode.ERR_InvalidExprTerm, Line = 3, Column = 25 },
new ErrorDescription { Code = (int)ErrorCode.ERR_InvalidExprTerm, Line = 3, Column = 30 }
);
CreateStandardCompilation(text).VerifyDiagnostics(
// (3,22): error CS0746: Invalid anonymous type member declarator. Anonymous type members must be declared with a member assignment, simple name or member access.
// object F = new { f1<int> = 1 };
Diagnostic(ErrorCode.ERR_InvalidAnonymousTypeMemberDeclarator, "f1<int> = 1").WithLocation(3, 22),
// (3,22): error CS0307: The property 'ClassA.f1' cannot be used with type arguments
// object F = new { f1<int> = 1 };
Diagnostic(ErrorCode.ERR_TypeArgsNotAllowed, "f1<int>").WithArguments("ClassA.f1", "property").WithLocation(3, 22));
}
[Fact]
......
// 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.Linq;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Roslyn.Test.Utilities;
using System.Linq;
using Xunit;
using Xunit.Abstractions;
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// #define PARSING_TESTS_DUMP
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册