From 41aad255fb3486337d168e33dc73f00f8abda824 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Wed, 22 Jul 2020 08:36:30 -0700 Subject: [PATCH] Allow semi-colon after record type constraints (#45943) --- .../Compiler Breaking Changes - DotNet 5.md | 14 +- ...Compiler Breaking Changes - post VS2019.md | 11 - .../CSharp/Portable/Parser/LanguageParser.cs | 23 +- .../CSharp/Test/Emit/Emit/EndToEndTests.cs | 12 +- .../Test/Semantic/Semantics/RecordTests.cs | 57 ++ .../Test/Syntax/Parsing/RecordParsing.cs | 775 +++++++++++++++++- 6 files changed, 861 insertions(+), 31 deletions(-) diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 5.md b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 5.md index 6902a25fd1cd0..a9f1a7a4b83d9 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 5.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 5.md @@ -1,4 +1,4 @@ -## This document lists known breaking changes in Roslyn in C# 9.0 which will be introduced with .NET 5. +## This document lists known breaking changes in Roslyn in C# 9.0 which will be introduced with .NET 5 (Visual Studio 2019 version 16.8). 1. Beginning with C# 9.0, when you switch on a value of type `byte` or `sbyte`, the compiler tracks which values have been handled and which have not. Technically, we do so for all numeric types, but in practice it is only a breaking change for the types `byte` and `sbyte`. For example, the following program contains a switch statement that explicitly handles *all* of the possible values of the switch's controlling expression ```csharp @@ -58,3 +58,15 @@ o is sbyte or short or int or long; ``` Because the `and` and `or` combinators can follow a type pattern, the compiler interprets them as part of the pattern combinator rather than an identifier for the declaration pattern. Consequently, it is an error to use `or` or `and` as pattern variable identifiers starting with C# 9.0. + +4. https://github.com/dotnet/roslyn/pull/44841 In *C# 9* and onwards the language views ambiguities between the `record` identifier as being + either a type syntax or a record declaration as choosing the record declaration. The following examples will now be record declarations: + + ```C# + abstract class C + { + record R2() { } + abstract record R3(); + } + ``` + diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - post VS2019.md b/docs/compilers/CSharp/Compiler Breaking Changes - post VS2019.md index e1aa2cc4dd328..35784ab476203 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - post VS2019.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - post VS2019.md @@ -120,14 +120,3 @@ public class Derived : Base public override string? M() { ... } // Derived.M doesn't honor the nullability declaration made by Base.M with its [NotNull] attribute } ``` - -19. https://github.com/dotnet/roslyn/pull/44841 In *C# 9* and onwards the language views ambiguities between the `record` identifier as being - either a type syntax or a record declaration as choosing the record declaration. The following examples will now be record declarations: - - ```C# - abstract class C - { - record R2() { } - abstract record R3(); - } - ``` \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 42cd41426b16b..c61461ae6c007 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -84,9 +84,10 @@ internal enum TerminatorState IsEndOfNameInExplicitInterface = 1 << 22, IsEndOfFunctionPointerParameterList = 1 << 23, IsEndOfFunctionPointerParameterListErrored = 1 << 24, + IsEndOfRecordSignature = 1 << 25, } - private const int LastTerminatorState = (int)TerminatorState.IsEndOfFunctionPointerParameterListErrored; + private const int LastTerminatorState = (int)TerminatorState.IsEndOfRecordSignature; private bool IsTerminator() { @@ -124,6 +125,7 @@ private bool IsTerminator() case TerminatorState.IsEndOfNameInExplicitInterface when this.IsEndOfNameInExplicitInterface(): case TerminatorState.IsEndOfFunctionPointerParameterList when this.IsEndOfFunctionPointerParameterList(errored: false): case TerminatorState.IsEndOfFunctionPointerParameterListErrored when this.IsEndOfFunctionPointerParameterList(errored: true): + case TerminatorState.IsEndOfRecordSignature when this.IsEndOfRecordSignature(): return true; } } @@ -1448,8 +1450,15 @@ private TypeDeclarationSyntax ParseClassOrStructOrInterfaceDeclaration(SyntaxLis var keyword = ConvertToKeyword(this.EatToken()); + var outerSaveTerm = _termState; + if (keyword.Kind == SyntaxKind.RecordKeyword) + { + _termState |= TerminatorState.IsEndOfRecordSignature; + } + var saveTerm = _termState; _termState |= TerminatorState.IsPossibleAggregateClauseStartOrStop; + var name = this.ParseIdentifierToken(); var typeParameters = this.ParseTypeParameterList(); @@ -1471,6 +1480,8 @@ private TypeDeclarationSyntax ParseClassOrStructOrInterfaceDeclaration(SyntaxLis this.ParseTypeParameterConstraintClauses(constraints); } + _termState = outerSaveTerm; + SyntaxToken semicolon; SyntaxToken? openBrace; SyntaxToken? closeBrace; @@ -1759,7 +1770,7 @@ private BaseListSyntax ParseBaseList(SyntaxToken typeKeyword, bool haveParameter while (true) { if (this.CurrentToken.Kind == SyntaxKind.OpenBraceToken || - this.CurrentToken.Kind == SyntaxKind.SemicolonToken || + ((_termState & TerminatorState.IsEndOfRecordSignature) != 0 && this.CurrentToken.Kind == SyntaxKind.SemicolonToken) || this.IsCurrentTokenWhereOfConstraintClause()) { break; @@ -1788,7 +1799,7 @@ private PostSkipAction SkipBadBaseListTokens(ref SyntaxToken colon, SeparatedSyn { return this.SkipBadSeparatedListTokensWithExpectedKind(ref colon, list, p => p.CurrentToken.Kind != SyntaxKind.CommaToken && !p.IsPossibleAttribute(), - p => p.CurrentToken.Kind == SyntaxKind.SemicolonToken || p.CurrentToken.Kind == SyntaxKind.OpenBraceToken || p.IsCurrentTokenWhereOfConstraintClause() || p.IsTerminator(), + p => p.CurrentToken.Kind == SyntaxKind.OpenBraceToken || p.IsCurrentTokenWhereOfConstraintClause() || p.IsTerminator(), expected); } @@ -1833,6 +1844,7 @@ private TypeParameterConstraintClauseSyntax ParseTypeParameterConstraintClause() while (true) { if (this.CurrentToken.Kind == SyntaxKind.OpenBraceToken + || ((_termState & TerminatorState.IsEndOfRecordSignature) != 0 && this.CurrentToken.Kind == SyntaxKind.SemicolonToken) || this.CurrentToken.Kind == SyntaxKind.EqualsGreaterThanToken || this.CurrentToken.ContextualKind == SyntaxKind.WhereKeyword) { @@ -3024,6 +3036,11 @@ private bool IsEndOfMethodSignature() return this.CurrentToken.Kind == SyntaxKind.SemicolonToken || this.CurrentToken.Kind == SyntaxKind.OpenBraceToken; } + private bool IsEndOfRecordSignature() + { + return this.CurrentToken.Kind == SyntaxKind.SemicolonToken || this.CurrentToken.Kind == SyntaxKind.OpenBraceToken; + } + private bool IsEndOfNameInExplicitInterface() { return this.CurrentToken.Kind == SyntaxKind.DotToken || this.CurrentToken.Kind == SyntaxKind.ColonColonToken; diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EndToEndTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EndToEndTests.cs index 550fccb14cf90..78aea607cb9ed 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EndToEndTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EndToEndTests.cs @@ -155,13 +155,13 @@ public void DeeplyNestedGeneric() int nestingLevel = (ExecutionConditionUtil.Architecture, ExecutionConditionUtil.Configuration) switch { // Legacy baselines are indicated by comments - (ExecutionArchitecture.x64, ExecutionConfiguration.Debug) when ExecutionConditionUtil.IsMacOS => 200, // 100 + (ExecutionArchitecture.x64, ExecutionConfiguration.Debug) when ExecutionConditionUtil.IsMacOS => 180, // 100 (ExecutionArchitecture.x64, ExecutionConfiguration.Release) when ExecutionConditionUtil.IsMacOS => 520, // 100 _ when ExecutionConditionUtil.IsCoreClrUnix => 1200, // 1200 _ when ExecutionConditionUtil.IsMonoDesktop => 730, // 730 - (ExecutionArchitecture.x86, ExecutionConfiguration.Debug) => 460, // 270 + (ExecutionArchitecture.x86, ExecutionConfiguration.Debug) => 450, // 270 (ExecutionArchitecture.x86, ExecutionConfiguration.Release) => 1290, // 1290 - (ExecutionArchitecture.x64, ExecutionConfiguration.Debug) => 260, // 170 + (ExecutionArchitecture.x64, ExecutionConfiguration.Debug) => 250, // 170 (ExecutionArchitecture.x64, ExecutionConfiguration.Release) => 730, // 730 _ => throw new Exception($"Unexpected configuration {ExecutionConditionUtil.Architecture} {ExecutionConditionUtil.Configuration}") }; @@ -217,7 +217,7 @@ public static void Main(string[] args) var source = builder.ToString(); RunInThread(() => { - var compilation = CreateCompilation(source, options: TestOptions.DebugExe); + var compilation = CreateCompilation(source, options: TestOptions.DebugExe.WithConcurrentBuild(false)); compilation.VerifyDiagnostics(); // PEVerify is skipped here as it doesn't scale to this level of nested generics. After @@ -266,7 +266,7 @@ static void Main() var source = builder.ToString(); RunInThread(() => { - var comp = CreateCompilation(source); + var comp = CreateCompilation(source, options: TestOptions.DebugDll.WithConcurrentBuild(false)); comp.VerifyDiagnostics(); }); } @@ -306,7 +306,7 @@ static void runTest(int n) RunInThread(() => { - var comp = CreateCompilation(source); + var comp = CreateCompilation(source, options: TestOptions.DebugDll.WithConcurrentBuild(false)); var type = comp.GetMember("C0"); var typeParameter = type.TypeParameters[0]; Assert.True(typeParameter.IsReferenceType); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs index d3c4d79a42b66..61961949b8fa4 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs @@ -18572,5 +18572,62 @@ protected C(C other) Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "P2").WithLocation(10, 15) ); } + + [Fact] + public void RecordWithConstraints_NullableWarning() + { + var src = @" +#nullable enable +record R(T P) where T : class; +record R2(T P) where T : class { } + +public class C +{ + public static void Main() + { + var r = new R(""R""); + var r2 = new R2(""R2""); + System.Console.Write((r.P, r2.P)); + } +}"; + + var comp = CreateCompilation(new[] { src, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.DebugExe); + comp.VerifyDiagnostics( + // (10,23): warning CS8634: The type 'string?' cannot be used as type parameter 'T' in the generic type or method 'R'. Nullability of type argument 'string?' doesn't match 'class' constraint. + // var r = new R("R"); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInTypeParameterReferenceTypeConstraint, "string?").WithArguments("R", "T", "string?").WithLocation(10, 23), + // (11,25): warning CS8634: The type 'string?' cannot be used as type parameter 'T' in the generic type or method 'R2'. Nullability of type argument 'string?' doesn't match 'class' constraint. + // var r2 = new R2("R2"); + Diagnostic(ErrorCode.WRN_NullabilityMismatchInTypeParameterReferenceTypeConstraint, "string?").WithArguments("R2", "T", "string?").WithLocation(11, 25) + ); + CompileAndVerify(comp, expectedOutput: "(R, R2)", verify: Verification.Skipped /* init-only */); + } + + [Fact] + public void RecordWithConstraints_ConstraintError() + { + var src = @" +record R(T P) where T : class; +record R2(T P) where T : class { } + +public class C +{ + public static void Main() + { + _ = new R(1); + _ = new R2(2); + } +}"; + + var comp = CreateCompilation(src); + comp.VerifyDiagnostics( + // (9,19): error CS0452: The type 'int' must be a reference type in order to use it as parameter 'T' in the generic type or method 'R' + // _ = new R(1); + Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "int").WithArguments("R", "T", "int").WithLocation(9, 19), + // (10,20): error CS0452: The type 'int' must be a reference type in order to use it as parameter 'T' in the generic type or method 'R2' + // _ = new R2(2); + Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "int").WithArguments("R2", "T", "int").WithLocation(10, 20) + ); + } } } diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/RecordParsing.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/RecordParsing.cs index 60c4e35205323..ad3b388fdcc42 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/RecordParsing.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/RecordParsing.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; using Xunit.Abstractions; @@ -408,6 +409,756 @@ abstract record R3(); EOF(); } + [Fact, WorkItem(45538, "https://github.com/dotnet/roslyn/issues/45538")] + public void RecordParsing_ConstraintAndSemiColon() + { + UsingTree("record R where T : class;"); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.IdentifierToken, "R"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.TypeParameterConstraintClause); + { + N(SyntaxKind.WhereKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.ClassConstraint); + { + N(SyntaxKind.ClassKeyword); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, WorkItem(45538, "https://github.com/dotnet/roslyn/issues/45538")] + public void RecordParsing_ConstraintAndSemiColon_MissingColon() + { + UsingTree("record R where T class;", + // (1,23): error CS1003: Syntax error, ':' expected + // record R where T class; + Diagnostic(ErrorCode.ERR_SyntaxError, "class").WithArguments(":", "class").WithLocation(1, 23) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.IdentifierToken, "R"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.TypeParameterConstraintClause); + { + N(SyntaxKind.WhereKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + M(SyntaxKind.ColonToken); + N(SyntaxKind.ClassConstraint); + { + N(SyntaxKind.ClassKeyword); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, WorkItem(45538, "https://github.com/dotnet/roslyn/issues/45538")] + public void RecordParsing_TwoConstraintsAndSemiColon() + { + UsingTree("record R where T1 : class where T2 : class;"); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.IdentifierToken, "R"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T1"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T2"); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.TypeParameterConstraintClause); + { + N(SyntaxKind.WhereKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T1"); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.ClassConstraint); + { + N(SyntaxKind.ClassKeyword); + } + } + N(SyntaxKind.TypeParameterConstraintClause); + { + N(SyntaxKind.WhereKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T2"); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.ClassConstraint); + { + N(SyntaxKind.ClassKeyword); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, WorkItem(45538, "https://github.com/dotnet/roslyn/issues/45538")] + public void RecordParsing_ConstraintAndSemiColon_Class() + { + UsingTree("abstract class C where T : class;", + // (1,36): error CS1003: Syntax error, ',' expected + // abstract class C where T : class; + Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(",", ";").WithLocation(1, 36), + // (1,37): error CS1514: { expected + // abstract class C where T : class; + Diagnostic(ErrorCode.ERR_LbraceExpected, "").WithLocation(1, 37), + // (1,37): error CS1513: } expected + // abstract class C where T : class; + Diagnostic(ErrorCode.ERR_RbraceExpected, "").WithLocation(1, 37) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.AbstractKeyword); + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.TypeParameterConstraintClause); + { + N(SyntaxKind.WhereKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.ClassConstraint); + { + N(SyntaxKind.ClassKeyword); + } + } + M(SyntaxKind.OpenBraceToken); + M(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, WorkItem(45538, "https://github.com/dotnet/roslyn/issues/45538")] + public void RecordParsing_TwoConstraintsAndSemiColon_Class() + { + UsingTree("abstract class C where T1 : class where T2 : class;", + // (1,59): error CS1003: Syntax error, ',' expected + // abstract class C where T1 : class where T2 : class; + Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(",", ";").WithLocation(1, 59), + // (1,60): error CS1514: { expected + // abstract class C where T1 : class where T2 : class; + Diagnostic(ErrorCode.ERR_LbraceExpected, "").WithLocation(1, 60), + // (1,60): error CS1513: } expected + // abstract class C where T1 : class where T2 : class; + Diagnostic(ErrorCode.ERR_RbraceExpected, "").WithLocation(1, 60) + ); + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.AbstractKeyword); + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T1"); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T2"); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.TypeParameterConstraintClause); + { + N(SyntaxKind.WhereKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T1"); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.ClassConstraint); + { + N(SyntaxKind.ClassKeyword); + } + } + N(SyntaxKind.TypeParameterConstraintClause); + { + N(SyntaxKind.WhereKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T2"); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.ClassConstraint); + { + N(SyntaxKind.ClassKeyword); + } + } + M(SyntaxKind.OpenBraceToken); + M(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, WorkItem(45538, "https://github.com/dotnet/roslyn/issues/45538")] + public void AbstractMethod_ConstraintsAndSemiColon() + { + UsingTree("abstract record R { abstract void M() where T : class; }"); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.AbstractKeyword); + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.IdentifierToken, "R"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.MethodDeclaration); + { + N(SyntaxKind.AbstractKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.VoidKeyword); + } + N(SyntaxKind.IdentifierToken, "M"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.ParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.TypeParameterConstraintClause); + { + N(SyntaxKind.WhereKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.ClassConstraint); + { + N(SyntaxKind.ClassKeyword); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void TestClassWithMultipleConstraints001() + { + UsingTree("class a where b : c where b { }", + // (1,32): error CS1003: Syntax error, ':' expected + // class a where b : c where b { } + Diagnostic(ErrorCode.ERR_SyntaxError, "{").WithArguments(":", "{").WithLocation(1, 32), + // (1,32): error CS1031: Type expected + // class a where b : c where b { } + Diagnostic(ErrorCode.ERR_TypeExpected, "{").WithLocation(1, 32) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "a"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.TypeParameterConstraintClause); + { + N(SyntaxKind.WhereKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.TypeConstraint); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + } + N(SyntaxKind.TypeParameterConstraintClause); + { + N(SyntaxKind.WhereKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + M(SyntaxKind.ColonToken); + M(SyntaxKind.TypeConstraint); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void TestClassWithMultipleConstraints002() + { + UsingTree("class a where b : c where { }", + // (1,30): error CS1001: Identifier expected + // class a where b : c where { } + Diagnostic(ErrorCode.ERR_IdentifierExpected, "{").WithLocation(1, 30), + // (1,30): error CS1003: Syntax error, ':' expected + // class a where b : c where { } + Diagnostic(ErrorCode.ERR_SyntaxError, "{").WithArguments(":", "{").WithLocation(1, 30), + // (1,30): error CS1031: Type expected + // class a where b : c where { } + Diagnostic(ErrorCode.ERR_TypeExpected, "{").WithLocation(1, 30) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "a"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.TypeParameterConstraintClause); + { + N(SyntaxKind.WhereKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.TypeConstraint); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + } + N(SyntaxKind.TypeParameterConstraintClause); + { + N(SyntaxKind.WhereKeyword); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.ColonToken); + M(SyntaxKind.TypeConstraint); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, WorkItem(45538, "https://github.com/dotnet/roslyn/issues/45538")] + public void RecordParsing_ConstraintsAndCurlyBraces() + { + UsingTree("record R where T : class { }"); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.IdentifierToken, "R"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.TypeParameterConstraintClause); + { + N(SyntaxKind.WhereKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.ClassConstraint); + { + N(SyntaxKind.ClassKeyword); + } + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, WorkItem(45538, "https://github.com/dotnet/roslyn/issues/45538")] + public void RecordParsing_ConstraintAndCommaAndSemiColon() + { + UsingTree("record R where T : class, ;", + // (1,30): error CS1031: Type expected + // record R where T : class, ; + Diagnostic(ErrorCode.ERR_TypeExpected, ";").WithLocation(1, 30) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.IdentifierToken, "R"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.TypeParameterConstraintClause); + { + N(SyntaxKind.WhereKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.ClassConstraint); + { + N(SyntaxKind.ClassKeyword); + } + N(SyntaxKind.CommaToken); + M(SyntaxKind.TypeConstraint); + { + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, WorkItem(45538, "https://github.com/dotnet/roslyn/issues/45538")] + public void RecordParsing_ConstraintAndCommaAndNewAndSemiColon() + { + UsingTree("record R where T : class, new();"); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.RecordDeclaration); + { + N(SyntaxKind.RecordKeyword); + N(SyntaxKind.IdentifierToken, "R"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.TypeParameterConstraintClause); + { + N(SyntaxKind.WhereKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.ColonToken); + N(SyntaxKind.ClassConstraint); + { + N(SyntaxKind.ClassKeyword); + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.ConstructorConstraint); + { + N(SyntaxKind.NewKeyword); + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void TestWhereWhere() + { + UsingTree("public class Goo : System.Object where where { }", + // (1,37): error CS1003: Syntax error, ',' expected + // public class Goo : System.Object where where { } + Diagnostic(ErrorCode.ERR_SyntaxError, "where").WithArguments(",", "").WithLocation(1, 37), + // (1,43): error CS1003: Syntax error, ',' expected + // public class Goo : System.Object where where { } + Diagnostic(ErrorCode.ERR_SyntaxError, "where").WithArguments(",", "").WithLocation(1, 43) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "Goo"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.BaseList); + { + N(SyntaxKind.ColonToken); + N(SyntaxKind.SimpleBaseType); + { + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "System"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Object"); + } + } + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.SimpleBaseType); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "where"); + } + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.SimpleBaseType); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "where"); + } + } + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact] + public void TestWhereWhereWhere() + { + UsingTree("public class Goo : System.Object where where where { }", + // (1,37): error CS1003: Syntax error, ',' expected + // public class Goo : System.Object where where where { } + Diagnostic(ErrorCode.ERR_SyntaxError, "where").WithArguments(",", "").WithLocation(1, 37), + // (1,43): error CS1003: Syntax error, ',' expected + // public class Goo : System.Object where where where { } + Diagnostic(ErrorCode.ERR_SyntaxError, "where").WithArguments(",", "").WithLocation(1, 43), + // (1,49): error CS1003: Syntax error, ',' expected + // public class Goo : System.Object where where where { } + Diagnostic(ErrorCode.ERR_SyntaxError, "where").WithArguments(",", "").WithLocation(1, 49) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "Goo"); + N(SyntaxKind.TypeParameterList); + { + N(SyntaxKind.LessThanToken); + N(SyntaxKind.TypeParameter); + { + N(SyntaxKind.IdentifierToken, "T"); + } + N(SyntaxKind.GreaterThanToken); + } + N(SyntaxKind.BaseList); + { + N(SyntaxKind.ColonToken); + N(SyntaxKind.SimpleBaseType); + { + N(SyntaxKind.QualifiedName); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "System"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Object"); + } + } + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.SimpleBaseType); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "where"); + } + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.SimpleBaseType); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "where"); + } + } + M(SyntaxKind.CommaToken); + N(SyntaxKind.SimpleBaseType); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "where"); + } + } + } + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + [Fact] public void WithParsingLangVer() { @@ -1650,12 +2401,15 @@ public void Base_03() { var text = "interface C : B;"; UsingTree(text, - // (1,16): error CS1514: { expected + // (1,16): error CS1003: Syntax error, ',' expected // interface C : B; - Diagnostic(ErrorCode.ERR_LbraceExpected, ";").WithLocation(1, 16), - // (1,16): error CS1513: } expected + Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(",", ";").WithLocation(1, 16), + // (1,17): error CS1514: { expected + // interface C : B; + Diagnostic(ErrorCode.ERR_LbraceExpected, "").WithLocation(1, 17), + // (1,17): error CS1513: } expected // interface C : B; - Diagnostic(ErrorCode.ERR_RbraceExpected, ";").WithLocation(1, 16) + Diagnostic(ErrorCode.ERR_RbraceExpected, "").WithLocation(1, 17) ); N(SyntaxKind.CompilationUnit); @@ -1677,7 +2431,6 @@ public void Base_03() } M(SyntaxKind.OpenBraceToken); M(SyntaxKind.CloseBraceToken); - N(SyntaxKind.SemicolonToken); } N(SyntaxKind.EndOfFileToken); } @@ -1780,12 +2533,15 @@ public void Base_05() // (1,16): error CS8861: Unexpected argument list. // interface C : B(X, Y); Diagnostic(ErrorCode.ERR_UnexpectedArgumentList, "(").WithLocation(1, 16), - // (1,22): error CS1514: { expected + // (1,22): error CS1003: Syntax error, ',' expected + // interface C : B(X, Y); + Diagnostic(ErrorCode.ERR_SyntaxError, ";").WithArguments(",", ";").WithLocation(1, 22), + // (1,23): error CS1514: { expected // interface C : B(X, Y); - Diagnostic(ErrorCode.ERR_LbraceExpected, ";").WithLocation(1, 22), - // (1,22): error CS1513: } expected + Diagnostic(ErrorCode.ERR_LbraceExpected, "").WithLocation(1, 23), + // (1,23): error CS1513: } expected // interface C : B(X, Y); - Diagnostic(ErrorCode.ERR_RbraceExpected, ";").WithLocation(1, 22) + Diagnostic(ErrorCode.ERR_RbraceExpected, "").WithLocation(1, 23) ); N(SyntaxKind.CompilationUnit); @@ -1827,7 +2583,6 @@ public void Base_05() } M(SyntaxKind.OpenBraceToken); M(SyntaxKind.CloseBraceToken); - N(SyntaxKind.SemicolonToken); } N(SyntaxKind.EndOfFileToken); }