dotnet / dotnet/roslyn

Test plan for "closed class hierarchies" feature

Open
#81,039 4 comments 0 reactions 3 assignees Claimed by @jjonescz View on GitHub
Area-Compilers Feature - Closed Class Hierarchies
Dominant language
C#
Stars
20.7k
Forks
4.3k
PR merge metrics
PR metrics pending

Description

Championed proposal: https://github.com/dotnet/csharplang/issues/9499
Speclet: https://github.com/dotnet/csharplang/blob/main/proposals/closed-hierarchies.md

- [ ] Syntax and Modifiers
- [x] No LangVersion error for `closed` modifier in C# 15 and above
- [x] LangVersion error for `closed` modifier when LangVersion below C# 15 is used.
(LangVersion, ClosedModifier_13, ClosedModifier_07_CSharp14, MemberNamedClosed_06_CSharp14, MemberNamedClosed_07_CSharp14, TypeNamedClosed_01_CSharp14, TypeNamedClosed_03_CSharp14,
Errors_01_CSharp14, Errors_02_CSharp14, Errors_03_CSharp14, Errors_04_CSharp14, MethodNamedRecord_01_CSharp8, MethodNamedRecord_02_CSharp8, ClosedRecord_01_CSharp8,
ClosedRecord_02_CSharp8, ClosedRecord_03_CSharp8, ClosedModifier_07_CSharp14, MemberNamedClosed_06_CSharp14, MemberNamedClosed_07_CSharp14, TypeNamedClosed_01_CSharp14,
TypeNamedClosed_03_CSharp14, Errors_01_CSharp14, Errors_02_CSharp14, Errors_03_CSharp14, Errors_04_CSharp14, MethodNamedRecord_01_CSharp8, MethodNamedRecord_02_CSharp8,
ClosedRecord_01_CSharp8, ClosedRecord_02_CSharp8, ClosedRecord_03_CSharp8)
- [ ] For class declarations
- [x] The `closed` modifier is syntactically valid on all syntactic forms of declarations
- [x] Regular class declaration (ClosedModifier_01_Valid with SyntaxKind.ClassKeyword)
- [x] Record declaration (ClosedModifier_01_Valid with SyntaxKind.RecordKeyword, ClosedModifier_04, ClosedRecord_01, ClosedRecord_02, ClosedRecord_03, ClosedRecord_04)
- [x] Record class declaration (ClosedModifier_05)
- [x] Cannot be combined with
- [x] `sealed` (Sealed_01)
- [x] `static` (Static_01)
- [x] `abstract` (Abstract_01, ClosedModifier_09, ClosedModifier_10)
- [x] Can be combined with other valid class modifiers (ClosedModifier_11, ClosedModifier_12_Valid)
- [ ] Cover partial declarations
- [ ] Both declarations specify modifier (ClosedModifier_02_Valid)
- [ ] Only one declaration specify modifier (first or second)
- [ ] `sealed`/`static`/`abstract` specified on part that doesn't specify `closed`, which is specified on the other part (first or second)
- [ ] The `closed` modifier is not valid on non-class declarations (BadTypeKind_01, ClosedModifier_01_Invalid, ClosedModifier_02_Invalid, ClosedModifier_02_Enum, ClosedModifier_11_Invalid, ClosedModifier_12_Invalid, ClosedModifier_14, ClosedModifier_18, ClosedMember_01, ClosedMember_02, ClosedMember_03, ClosedMember_04, ClosedMember_05)
- [ ] All forms of struct declarations (BadTypeKind_01, ClosedModifier_01_Invalid with SyntaxKind.StructKeyword, ClosedModifier_11_Invalid, ClosedModifier_12_Invalid)
- [x] `struct` (BadTypeKind_01, ClosedModifier_01_Invalid with SyntaxKind.StructKeyword)
- [ ] `record struct` (ClosedModifier_06)
- [ ] `union`
- [x] `enum` (BadTypeKind_01, ClosedModifier_01_Invalid with SyntaxKind.EnumKeyword, ClosedModifier_02_Enum, ClosedModifier_11_Invalid, ClosedModifier_12_Invalid, BadTypeKind_01)
- [x] `delegate` (BadTypeKind_01, ClosedModifier_14)
- [x] `interface` (BadTypeKind_01, ClosedModifier_01_Invalid with SyntaxKind.InterfaceKeyword, ClosedModifier_02_Invalid, ClosedModifier_03, ClosedModifier_11_Invalid, ClosedModifier_12_Invalid)
- [x] `namespace` (Errors_03, Errors_03_CSharp14, Errors_04, Errors_04_CSharp14)
- [ ] Member declarations (BadTypeKind_01, ClosedMember_01, ClosedMember_02, ClosedMember_03, ClosedMember_04, ClosedMember_05, ClosedModifier_18, ParameterModifier, ParameterType)
- [ ] Constructor (MethodNamedRecord_01, MethodNamedRecord_02)
- [ ] Destructor
- [x] Method (ClosedMember_01, ClosedMember_05, MethodNamedRecord_01, MethodNamedRecord_02, ClosedRecord_01, ClosedRecord_02, ClosedModifier_18, BadTypeKind_01)
- [x] Property (ClosedMember_04, MemberNamedClosed_02, BadTypeKind_01)
- [ ] get accessor
- [ ] set accessor
- [ ] init accessor
- [ ] Event (ClosedMember_03, MemberNamedClosed_03, BadTypeKind_01)
- [ ] add accessor
- [ ] remove accessor
- [x] Field (ClosedMember_02, MemberNamedClosed_01, ClosedRecord_03, BadTypeKind_01)
- [x] Field-like event (ClosedMember_03, BadTypeKind_01)
- [ ] Local variable (LocalVariable_01, LocalVariable_02, Variable_01, TopLevelVariable_01, TopLevelVariable_02, TopLevelVariable_03)
- [ ] Local function (LambdaReturn, LocalFunctionReturn)

- [x] A closed class is implicitly abstract (ImplicitlyAbstract_01, ImplicitlyAbstract_02, ImplicitlyAbstract_03, Sealed_01, Static_01, Abstract_01, Symbols_01, Symbols_02)
- [ ] A class derived from a closed class
- [ ] Is not implicitly closed (Subtypes_01)
- [x] Can be marked closed explicitly (BaseTypeFromMetadata_05, Exhaustiveness_06)
- [ ] A class directly or indirectly nested into a closed class is not implicitly closed (ClosedModifier_17)
- [x] A nested class can be marked closed (ClosedModifier_17, Exhaustiveness_07)
- [x] A class cannot directly inherit from a closed class declared in a different .NET module (BaseTypeFromMetadata_01, BaseTypeFromMetadata_02)
- [x] Direct derivation from closed class in different assembly (BaseTypeFromMetadata_01, BaseTypeFromMetadata_05)
- [x] Cover compilation reference (BaseTypeFromMetadata_01, BaseTypeFromMetadata_04, BaseTypeFromMetadata_05)
- [x] Cover metadata image reference (BaseTypeFromMetadata_01, BaseTypeFromMetadata_04, BaseTypeFromMetadata_05)
- [x] Direct derivation from closed class in different module within the same multi-module assembly (BaseTypeFromMetadata_02)
- [x] Direct derivation from closed class in same .NET module is allowed (Subtypes_01, Exhaustiveness_01)
- [x] Indirect derivation (deriving from a non-closed subclass) from another assembly is allowed (BaseTypeFromMetadata_04)
- [x] Cover compilation reference (BaseTypeFromMetadata_04, Exhaustiveness_LessAccessibleSubtype_01, Exhaustiveness_LessAccessibleSubtype_02, Exhaustiveness_LessAccessibleSubtype_03, Exhaustiveness_LessAccessibleSubtype_04, Exhaustiveness_LessAccessibleSubtype_05, Exhaustiveness_LessAccessibleSubtype_06)
- [x] Cover metadata image reference (BaseTypeFromMetadata_04, Exhaustiveness_LessAccessibleSubtype_01, Exhaustiveness_LessAccessibleSubtype_02, Exhaustiveness_LessAccessibleSubtype_03, Exhaustiveness_LessAccessibleSubtype_04, Exhaustiveness_LessAccessibleSubtype_05, Exhaustiveness_LessAccessibleSubtype_06)
- [ ] Indirect derivation (deriving from a non-closed subclass) from another module is allowed
- [ ] If a class directly derives from a closed class, then all of its type parameters must be used in the base class specification (GenericSubtype_01, GenericSubtype_02, GenericSubtype_03, GenericSubtype_04, GenericSubtype_05, GenericSubtype_06, GenericSubtype_07)
- [x] The requirement is applied to generic type parameters of the derived class itself (GenericSubtype_01, GenericSubtype_02, GenericSubtype_06)
- [x] The requirement is applied to generic type parameters of any enclosing type (GenericSubtype_03, GenericSubtype_04)
- [ ] Valid Generic Derivations (GenericSubtype_01, GenericSubtype_03, GenericSubtype_05)
- [x] Generic class deriving from closed generic class where all type parameters are used directly in base class (e.g., class D : C) (GenericSubtype_01)
- [x] Generic class deriving from closed generic class where type parameters are used within constructed types (e.g., class D : C) (GenericSubtype_01, GenericSubtype_03)
- [ ] Multiple type parameters all used in base class specification (GenericSubtype_01, GenericSubtype_05, GenericSubtype_06)
- [x] Invalid Generic Derivations (GenericSubtype_02, GenericSubtype_04, GenericSubtype_06, GenericSubtype_07)
- [x] Error: Generic class deriving from closed generic class where a type parameter is not used in base class (e.g., class D : C) (GenericSubtype_02)
- [x] Error: Generic class with multiple type parameters where only some are used in base class (GenericSubtype_06)
- [x] It is not an error to not declare any derived types for a closed type (Exhaustiveness_NoSubtypes, Exhaustiveness_OnlyClosedSubtypes, GenericSubtype_07, Exhaustiveness_NoSubtypes)

- [ ] A switch expression that handles all of the direct descendants of a closed class will be considered to have exhausted that class. (Exhaustiveness_01, Exhaustiveness_02, Exhaustiveness_03, Exhaustiveness_04, Exhaustiveness_05, Exhaustiveness_06, Exhaustiveness_07, Exhaustiveness_UnionOfClosedClasses_01, Exhaustiveness_UnionOfClosedClasses_02, Exhaustiveness_UnionOfClosedClasses_03, Exhaustiveness_ClosedClassCustomUnion_01, Exhaustiveness_ClosedClassCustomUnion_02, Exhaustiveness_ClosedClassCustomUnion_03, Exhaustiveness_Generic_01, Exhaustiveness_Generic_02, Exhaustiveness_Generic_03, Exhaustiveness_Generic_04, Exhaustiveness_Generic_NonGenericSubtypes_01, Exhaustiveness_NoSubtypes, Exhaustiveness_BaseTypeSubsumedBySubtypes_01, Exhaustiveness_BaseTypeSubsumedBySubtypes_02, Exhaustiveness_BaseTypeSubsumedBySubtypes_03, Exhaustiveness_BaseTypeSubsumedBySubtypes_04, Exhaustiveness_OnlyClosedSubtypes, Exhaustiveness_LessAccessibleSubtype_01, Exhaustiveness_LessAccessibleSubtype_02, Exhaustiveness_LessAccessibleSubtype_03, Exhaustiveness_LessAccessibleSubtype_04, Exhaustiveness_LessAccessibleSubtype_05, Exhaustiveness_LessAccessibleSubtype_06, Exhaustiveness_Constraints_01, Exhaustiveness_Constraints_02, Exhaustiveness_Constraints_03, Exhaustiveness_Constraints_04, Exhaustiveness_Constraints_05, Exhaustiveness_Constraints_06, Exhaustiveness_Constraints_07, Exhaustiveness_Constraints_08, Exhaustiveness_Constraints_09, Exhaustiveness_Constraints_09_SubtypeFirst, Exhaustiveness_Constraints_10, Exhaustiveness_InterfaceConstraints_01, Exhaustiveness_InterfaceConstraints_02, Exhaustiveness_InterfaceConstraints_03, Exhaustiveness_GenericContainingType_01, Exhaustiveness_GenericNestedType_01, Exhaustiveness_ConstrainedToClosedType_01, Exhaustiveness_ConstrainedToClosedType_02, Exhaustiveness_BaseTypeArguments_Array_01, Exhaustiveness_BaseTypeArguments_Array_02, Exhaustiveness_BaseTypeArguments_Tuple)
- [x] Switch expression covering all direct descendants of closed class is exhaustive (Exhaustiveness_01, Exhaustiveness_03, Exhaustiveness_06)
- [x] Cover "recursive" scenario. I.e. when a direct descendant is a closed class, covering all its descendants should be sufficient with respect to exhaustiveness through that descendant. (Exhaustiveness_05, Exhaustiveness_06, Exhaustiveness_07, Exhaustiveness_UnionOfClosedClasses_01, Exhaustiveness_OnlyClosedSubtypes)
- [x] Switch expression missing a direct descendant and the closed class it self is not exhaustive (Exhaustiveness_02, Exhaustiveness_04, Exhaustiveness_05, Exhaustiveness_07, Exhaustiveness_NoSubtypes)
- [x] An exhaustive switch only needs to specify cases for derived types that are actually possible for the given instantiation of the closed type. (Exhaustiveness_Generic_02, Exhaustiveness_Generic_NonGenericSubtypes_01, Exhaustiveness_Constraints_01, Exhaustiveness_Constraints_02, Exhaustiveness_Constraints_03, Exhaustiveness_Constraints_04, Exhaustiveness_Constraints_05, Exhaustiveness_Constraints_06, Exhaustiveness_Constraints_07, Exhaustiveness_InterfaceConstraints_01, Exhaustiveness_InterfaceConstraints_02, Exhaustiveness_InterfaceConstraints_03, Exhaustiveness_BaseTypeArguments_Array_01, Exhaustiveness_BaseTypeArguments_Array_02, Exhaustiveness_BaseTypeArguments_Tuple)
- [x] Cover scenarios when all possible subtypes cannot be covered (not exhaustive unless there is a pattern for the closed class itself) (Exhaustiveness_Generic_02, Exhaustiveness_Generic_03, Exhaustiveness_Generic_04, Exhaustiveness_LessAccessibleSubtype_01, Exhaustiveness_LessAccessibleSubtype_02, Exhaustiveness_LessAccessibleSubtype_03, Exhaustiveness_LessAccessibleSubtype_04, Exhaustiveness_LessAccessibleSubtype_05, Exhaustiveness_LessAccessibleSubtype_06, Exhaustiveness_Constraints_01, Exhaustiveness_Constraints_02, Exhaustiveness_Constraints_03, Exhaustiveness_Constraints_04, Exhaustiveness_Constraints_05, Exhaustiveness_Constraints_06, Exhaustiveness_Constraints_07, Exhaustiveness_Constraints_08, Exhaustiveness_Constraints_09, Exhaustiveness_Constraints_09_SubtypeFirst, Exhaustiveness_Constraints_10, Exhaustiveness_InterfaceConstraints_01, Exhaustiveness_InterfaceConstraints_02)
- [x] Due to accessiblity (Exhaustiveness_LessAccessibleSubtype_01, Exhaustiveness_LessAccessibleSubtype_02, Exhaustiveness_LessAccessibleSubtype_03, Exhaustiveness_LessAccessibleSubtype_04, Exhaustiveness_LessAccessibleSubtype_05, Exhaustiveness_LessAccessibleSubtype_06)
- [x] Due to constraint violations (Exhaustiveness_Constraints_01, Exhaustiveness_Constraints_02, Exhaustiveness_Constraints_03, Exhaustiveness_Constraints_04, Exhaustiveness_Constraints_05, Exhaustiveness_Constraints_06, Exhaustiveness_Constraints_07, Exhaustiveness_Constraints_08, Exhaustiveness_Constraints_09, Exhaustiveness_Constraints_09_SubtypeFirst, Exhaustiveness_Constraints_10, Exhaustiveness_InterfaceConstraints_01, Exhaustiveness_InterfaceConstraints_02, Exhaustiveness_BaseTypeArguments_Tuple)
- [x] Other "unspeakable" derived classes (Exhaustiveness_Generic_02, Exhaustiveness_Generic_03, Exhaustiveness_Generic_04, Exhaustiveness_Constraints_05, Exhaustiveness_Constraints_06, Exhaustiveness_Constraints_07, Exhaustiveness_BaseTypeArguments_Array_01, Exhaustiveness_BaseTypeArguments_Array_02, Exhaustiveness_BaseTypeArguments_Tuple)
- [ ] When LangVersion is below C# 15 either a languge version error should be reported for scenarios when switch wouldn't be considered exhaustive by C# 14 compiler, or the switch shouldn't be considered exhaustive.
- [x] Switch on type parameter constrained to a closed type is non-exhaustive even when covering all subtypes (Exhaustiveness_ConstrainedToClosedType_01, Exhaustiveness_ConstrainedToClosedType_02)
- [x] Error: Closed base class pattern after all descendants is unreachable (Exhaustiveness_BaseTypeSubsumedBySubtypes_01, Exhaustiveness_BaseTypeSubsumedBySubtypes_02, Exhaustiveness_BaseTypeSubsumedBySubtypes_03, Exhaustiveness_BaseTypeSubsumedBySubtypes_04)

- [ ] Cover interaction union type. (Exhaustiveness_UnionOfClosedClasses_01, Exhaustiveness_UnionOfClosedClasses_02, Exhaustiveness_UnionOfClosedClasses_03, Exhaustiveness_ClosedClassCustomUnion_01, Exhaustiveness_ClosedClassCustomUnion_02, Exhaustiveness_ClosedClassCustomUnion_03, Exhaustiveness_Constraints_08, Exhaustiveness_Constraints_09, Exhaustiveness_Constraints_09_SubtypeFirst, Exhaustiveness_Constraints_10)
- [x] A union with a closed class as a case type (Exhaustiveness_UnionOfClosedClasses_01, Exhaustiveness_UnionOfClosedClasses_02, Exhaustiveness_UnionOfClosedClasses_03, Exhaustiveness_Constraints_08, Exhaustiveness_Constraints_09, Exhaustiveness_Constraints_09_SubtypeFirst, Exhaustiveness_Constraints_10)
- [x] Covering all direct descendants of the closed class is exhaustive with respect to that case type. (Exhaustiveness_UnionOfClosedClasses_01)
- [x] Cover a scenario with a case type that is closed type with a closed descendant ("recursive" scenario). Covering all its descendants of that descendant should be sufficient with respect to exhaustiveness through that descendant. (Exhaustiveness_UnionOfClosedClasses_01, Exhaustiveness_UnionOfClosedClasses_02)
- [ ] A closed class with direct descendant that is a union type (Exhaustiveness_ClosedClassCustomUnion_01, Exhaustiveness_ClosedClassCustomUnion_02, Exhaustiveness_ClosedClassCustomUnion_03)
- [ ] Covering all the case types isn't equivalent to covering union type with respect to exhaustiveness through that descendant for the closed class. In other words, union type isn't unwrapped. (Exhaustiveness_ClosedClassCustomUnion_01, Exhaustiveness_ClosedClassCustomUnion_02, Exhaustiveness_ClosedClassCustomUnion_03)
- [x] A closed class is a Union. Matching uses "Union" path

- [ ] Interaction with records
- [ ] Base class of a record must be a record or an object. Closed modifier on base or derived class doesn't change that
- [ ] Closed record cannot be directly inherited from in a different module.

- [x] Cover pattern matching scenarios for a closed type without sub-types. (Exhaustiveness_NoSubtypes, Exhaustiveness_OnlyClosedSubtypes, Exhaustiveness_UnionOfClosedClasses_03)
- [x] Cover pattern matching scenarios for a closed type with one sub-type. (Exhaustiveness_OnlyClosedSubtypes)
- [x] Cover pattern matching scenarios for a closed type with more than one sub-type. (Exhaustiveness_01, Exhaustiveness_02, Exhaustiveness_03, Exhaustiveness_04, Exhaustiveness_05, Exhaustiveness_06, Exhaustiveness_07, Exhaustiveness_Generic_01, Exhaustiveness_Generic_NonGenericSubtypes_01, Exhaustiveness_BaseTypeSubsumedBySubtypes_01, Exhaustiveness_BaseTypeSubsumedBySubtypes_02, Exhaustiveness_BaseTypeSubsumedBySubtypes_03, Exhaustiveness_BaseTypeSubsumedBySubtypes_04, Exhaustiveness_OnlyClosedSubtypes)

- [ ] Interface Convertibility (Sealed Hierarchy).
- [ ] Sealed Hierarchy Detection
- [ ] Closed class with all descendants that are either sealed, or having a sealed hierarchy has a sealed hierarchy
- [ ] Closed class with at least one non-sealed, non-closed descendant does not have a sealed hierarchy
- [ ] Closed class with at least one closed descendant that doesn't have sealed hierarchy does not have a sealed hierarchy
- [ ] For a closed class with sealed hierarchy
- [ ] If the class is not known to implement an interface and none of the leaves in the sealed hierarchy are known to implement the interface, then there is no implicit or explicit conversion to that interface from the class.
- [ ] If the class is known to implement an interface, then there is an implicit conversion to that interface from the class.
- [ ] If at least one leaf type from the sealed hierarhy is known to implement the interface, then there is at least an explicit conversion to that interface from the class.
- [ ] If all leaves in the sealed hierarhy are known to implement an interface, then there is an implicit conversion to that interface from the class.
- [ ] Cover scenario for a closed type without sub-types.
- [ ] Cover scenario for a closed type with one sub-type.
- [ ] Cover scenario for a closed type with more than one sub-type.
- [ ] No change in conversion classification with respect to conversion to an interface for closed classes that do not have a sealed hierarchy
- [ ] No change in conversion classification with respect to conversion to an interface for not-closed sealed classes
- [ ] No change in conversion classification with respect to conversion to an interface for not-closed not-sealed classes

- [ ] Metadata emit (Symbols_01, Symbols_02, DoesNotSynthesizeAttribute_01, DoesNotSynthesizeAttribute_02, RequiredMembers_01, RequiredMembers_02, ClosedAttributeExplicitUsage, ConsumeFromVB_01, ConsumeFromVB_02)
- [x] ClosedAttribute is emitted on a closed class (Symbols_01, Symbols_02)
- [x] Explicit application of a ClosedAttribute is disallowed for any attribute target (ClosedAttributeExplicitUsage)
- [ ] `[CompilerFeatureRequired("ClosedClasses")]` (Symbols_01, Symbols_02, DoesNotSynthesizeAttribute_01, DoesNotSynthesizeAttribute_02, RequiredMembers_01, RequiredMembers_02, CompilerFeatureRequired_NonClosedContainingType, ConsumeFromVB_01, ConsumeFromVB_02)
- [x] Is emitted on constructors of a closed class (Symbols_01, Symbols_02)
- [ ] Is emitted even for private constructors
- [x] Is not emitted on the closed class itself (Symbols_01, Symbols_02, RequiredMembers_01, RequiredMembers_02)
- [x] ObsoleteAttribute is not implicitly emitted on constructors of a closed class (Symbols_01, Symbols_02)
- [x] CompilerFeatureRequired for other features are also emitted on constructors as appropriate when those features are used. In other words, compiler properly emits multiple applications of the attribute when necessary. (RequiredMembers_01, RequiredMembers_02)
- [x] Confirm VB is unable to inherit from a closed class (ConsumeFromVB_01, ConsumeFromVB_02)
- [ ] Confirm VB program can consume/access closed class instances and reference the type in other scenarios
- [ ] Manual testing: Confirm old compiler reports error when trying to derive from closed class
- [x] An error is reported if ClosedAttribute is not found

- [ ] Metadata loading (Symbols_01, Subtypes_01, Subtypes_02, Subtypes_03, Subtypes_04, Subtypes_05, Subtypes_Retargeting_01, BaseTypeFromMetadata_01, BaseTypeFromMetadata_03, BaseTypeFromMetadata_04, BaseTypeFromMetadata_05, CompilerFeatureRequired_NonClosedContainingType, Exhaustiveness_LessAccessibleSubtype_03)
- [x] Classes with ClosedAttribute are considered closed (Symbols_01, BaseTypeFromMetadata_01, BaseTypeFromMetadata_03, BaseTypeFromMetadata_04, BaseTypeFromMetadata_05, ImplicitlyAbstract_01)
- [x] The ClosedAttribute is removed from set of attributes returned by `GetAttributes` API (Symbols_01, Symbols_02)
- [ ] If namespace of ClosedAttribute is wrong the attribute is not recognized or removed, the class imported as not-closed
- [ ] If constructor signature of ClosedAttribute is wrong the attribute is not recognized or removed, the class imported as not-closed

- [x] Confirm new internal APIs are working properly in retargeting scenarios (Subtypes_Retargeting_01)

- [ ] Custom modifiers containing type parameters scenario (https://github.com/dotnet/roslyn/pull/83493#discussion_r3184511449)

- [ ] Test behavior of syntax normalizer, it should properly handle whitespace around the `closed` keyword.

- [ ] Adding required APIs to BCL
- [ ] ClosedAttribute

- [ ] New public API
- [ ] Get approval for public API changes/additions
- [ ] Test behavior change for the affected APIs

- [ ] Update compiler test plan

- [ ] IDE test pass
- [x] `closed` keyword classification/colorization
- [x] `closed` keyword suggestion
- [ ] Typing
- [ ] Formatting
- [ ] F1 help for `closed` keyword
- [ ] If new dedicated LangVersion diagnostic is added, verify version upgrade is suggested when it is reported.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.