diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs index ba9c1c76ae68f..69c3de99c3b5a 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs @@ -77,6 +77,9 @@ public enum SyntaxKind : ushort /// Represents .. token. DotDotToken = 8222, + // Values ranging from 8193 (TildeToken) to 8287 (GreaterThanGreaterThanGreaterThanEqualsToken) are reserved for punctuation kinds. + // This gap is included within that range. So if you add a value here make sure `SyntaxFacts.GetPunctuationKinds` includes it in the returned enumeration + // additional xml tokens /// Represents /> token. SlashGreaterThanToken = 8232, // xml empty element end @@ -95,6 +98,9 @@ public enum SyntaxKind : ushort /// Represents ?> token. XmlProcessingInstructionEndToken = 8239, // ?> + // Values ranging from 8193 (TildeToken) to 8287 (GreaterThanGreaterThanGreaterThanEqualsToken) are reserved for punctuation kinds. + // This gap is included within that range. So if you add a value here make sure `SyntaxFacts.GetPunctuationKinds` includes it in the returned enumeration + // compound punctuation /// Represents || token. BarBarToken = 8260, diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs index 652092a89b94f..6a19e29e2e469 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; +using System.Diagnostics; namespace Microsoft.CodeAnalysis.CSharp { @@ -17,6 +19,7 @@ public static IEnumerable GetReservedKeywordKinds() { for (int i = (int)SyntaxKind.BoolKeyword; i <= (int)SyntaxKind.ImplicitKeyword; i++) { + Debug.Assert(Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i)); yield return (SyntaxKind)i; } } @@ -142,9 +145,10 @@ public static IEnumerable GetPreprocessorKeywordKinds() yield return SyntaxKind.TrueKeyword; yield return SyntaxKind.FalseKeyword; yield return SyntaxKind.DefaultKeyword; - yield return SyntaxKind.HiddenKeyword; + for (int i = (int)SyntaxKind.ElifKeyword; i <= (int)SyntaxKind.RestoreKeyword; i++) { + Debug.Assert(Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i)); yield return (SyntaxKind)i; } } @@ -172,10 +176,26 @@ private static bool IsDebuggerSpecialPunctuation(SyntaxKind kind) public static IEnumerable GetPunctuationKinds() { - for (int i = (int)SyntaxKind.TildeToken; i <= (int)SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken; i++) + for (int i = (int)SyntaxKind.TildeToken; i <= (int)SyntaxKind.DotDotToken; i++) + { + Debug.Assert(Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i)); + yield return (SyntaxKind)i; + } + + for (int i = (int)SyntaxKind.SlashGreaterThanToken; i <= (int)SyntaxKind.XmlProcessingInstructionEndToken; i++) { + Debug.Assert(Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i)); yield return (SyntaxKind)i; } + + for (int i = (int)SyntaxKind.BarBarToken; i <= (int)SyntaxKind.QuestionQuestionEqualsToken; i++) + { + Debug.Assert(Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i)); + yield return (SyntaxKind)i; + } + + yield return SyntaxKind.GreaterThanGreaterThanGreaterThanToken; + yield return SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken; } public static bool IsPunctuationOrKeyword(SyntaxKind kind) @@ -1148,7 +1168,12 @@ public static IEnumerable GetContextualKeywordKinds() { for (int i = (int)SyntaxKind.YieldKeyword; i <= (int)SyntaxKind.FileKeyword; i++) { - yield return (SyntaxKind)i; + // 8441 corresponds to a deleted kind (DataKeyword) that was previously shipped. + if (i != 8441) + { + Debug.Assert(Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i)); + yield return (SyntaxKind)i; + } } } diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxTests.cs index f8f0d20bfe449..b9d54c3d01f96 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxTests.cs @@ -5,6 +5,9 @@ #nullable disable using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; @@ -276,5 +279,68 @@ public void IsAttributeTargetSpecifier() } } } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72300")] + public void TestAllKindsReturnedFromGetKindsMethodsExist() + { + foreach (var method in typeof(SyntaxFacts).GetMethods(BindingFlags.Public | BindingFlags.Static)) + { + if (method.ReturnType == typeof(IEnumerable) && method.GetParameters() is []) + { + foreach (var kind in (IEnumerable)method.Invoke(null, null)) + { + Assert.True(Enum.IsDefined(typeof(SyntaxKind), kind), $"Nonexistent kind '{kind}' returned from method '{method.Name}'"); + } + } + } + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72300")] + [InlineData(nameof(SyntaxFacts.GetContextualKeywordKinds), SyntaxKind.YieldKeyword, SyntaxKind.ElifKeyword)] + [InlineData(nameof(SyntaxFacts.GetPunctuationKinds), SyntaxKind.TildeToken, SyntaxKind.BoolKeyword)] + [InlineData(nameof(SyntaxFacts.GetReservedKeywordKinds), SyntaxKind.BoolKeyword, SyntaxKind.YieldKeyword)] + public void TestRangeBasedGetKindsMethodsReturnExpectedResults(string methodName, SyntaxKind lowerBoundInclusive, SyntaxKind upperBoundExclusive) + { + var method = typeof(SyntaxFacts).GetMethod(methodName, BindingFlags.Public | BindingFlags.Static); + + Assert.NotNull(method); + Assert.Equal(0, method.GetParameters().Length); + Assert.Equal(typeof(IEnumerable), method.ReturnType); + + var returnedKindsInts = ((IEnumerable)method.Invoke(null, null)).Select(static k => (int)k).ToHashSet(); + + for (int i = (int)lowerBoundInclusive; i < (int)upperBoundExclusive; i++) + { + if (Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i)) + { + Assert.True(returnedKindsInts.Remove(i)); + } + else + { + Assert.DoesNotContain(i, returnedKindsInts); + } + } + + // We've already removed all expected kinds from the set. It should be empty now + Assert.Empty(returnedKindsInts); + } + + [Fact] + public void TestGetPreprocessorKeywordKindsReturnsExpectedResults() + { + var returnedKindsInts = SyntaxFacts.GetPreprocessorKeywordKinds().Select(static k => (int)k).ToHashSet(); + + Assert.True(returnedKindsInts.Remove((int)SyntaxKind.TrueKeyword)); + Assert.True(returnedKindsInts.Remove((int)SyntaxKind.FalseKeyword)); + Assert.True(returnedKindsInts.Remove((int)SyntaxKind.DefaultKeyword)); + + for (int i = (int)SyntaxKind.ElifKeyword; i < (int)SyntaxKind.ReferenceKeyword; i++) + { + Assert.True(returnedKindsInts.Remove(i)); + } + + // We've already removed all expected kinds from the set. It should be empty now + Assert.Empty(returnedKindsInts); + } } } diff --git a/src/Compilers/VisualBasic/Test/Syntax/Syntax/SyntaxFactsTest.vb b/src/Compilers/VisualBasic/Test/Syntax/Syntax/SyntaxFactsTest.vb index 3b82a4a5be9fd..0c1d011590a7e 100644 --- a/src/Compilers/VisualBasic/Test/Syntax/Syntax/SyntaxFactsTest.vb +++ b/src/Compilers/VisualBasic/Test/Syntax/Syntax/SyntaxFactsTest.vb @@ -3,6 +3,7 @@ ' See the LICENSE file in the project root for more information. Imports System.IO +Imports System.Reflection Imports System.Text Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.VisualBasic @@ -1262,4 +1263,15 @@ End Module Assert.Equal(isReserved, SyntaxFacts.IsReservedTupleElementName(elementName)) End Sub + + Public Sub TestAllKindsReturnedFromGetKindsMethodsExist() + For Each method In GetType(SyntaxFacts).GetMethods(BindingFlags.Public Or BindingFlags.Static) + If method.ReturnType = GetType(IEnumerable(Of SyntaxKind)) AndAlso method.GetParameters().Length = 0 Then + For Each kind As SyntaxKind In DirectCast(method.Invoke(Nothing, Nothing), IEnumerable(Of SyntaxKind)) + Assert.True([Enum].IsDefined(GetType(SyntaxKind), kind), $"Nonexistent kind '{kind}' returned from method '{method.Name}'") + Next + End If + Next + End Sub + End Class