dotnet / dotnet/roslyn

Test plan for "Unions"

Open
#81,074 2 comments 6 reactions 1 assignee Claimed by @AlekseyTs View on GitHub
Area-Compilers Feature - Unions
Dominant language
C#
Stars
20.7k
Forks
4.3k
PR merge metrics
PR metrics pending

Description

## Unions Test Plan

Championed proposal: https://github.com/dotnet/csharplang/issues/9662
Speclet: https://github.com/dotnet/csharplang/blob/main/proposals/unions.md
Feature branch: https://github.com/dotnet/roslyn/tree/features/Unions

- [x] LangVersion: block union matching (`UnionMatching_05_Constant_01`)
- LangVersion: conditional parsing of `union` declarations
- [x] At top-level (`UnionDeclaration_35_LanguageVersion`)
- [ ] As a namespace or type member
- [ ] Add unions to Compiler Test Plan
- [ ] Interactions with "pure null tests" in is-patterns, e.g.:
- `MyUnion myUnion = null; Console.WriteLine(myUnion is object);`
- `MyUnion myUnion = new MyUnion(new object()); Console.WriteLine(myUnion is object);`
- `MyUnion myUnion = new MyUnion(""); Console.WriteLine(myUnion is object);`
- `MyUnion myUnion = null; if (myUnion is object) { myUnion.ToString(); } else { myUnion.ToString(); }`
- [ ] caching for `UnionCaseTypes`?
- [ ] Unconventional `UnionAttribuite` definitions, like https://github.com/dotnet/roslyn/pull/82408#discussion_r2824589525
- [ ] Exhaustiveness
- [ ] Matching all case types prevents an exhaustiveness warning
- [ ] Exhaustiveness diagnostics can suggest a specific unmatched case type
- [ ] Attempting to match `object`, after individually matching all case types, reports an error
- [ ] Unions containing union types as case types, where the nested union is exhausted by checking the nested case types. e.g. `union Parent(int, Child); union Child(string, bool); switch (new Parent()) { int => 1, string => 2, bool => 3 }`
- [ ] nullability analysis
- [ ] `IUnion.Value` without nullable annotation (`object!`)
- [ ] `union is null` expands to `union is not null && union.Value is not null` for reference type and `System.Nullable`. (both flow analysis and semantics tests)
- [ ] `object? Value { get; }` annotation determines initial flow state e.g. of `MyUnion param`
- [x] BCL: add `IUnion` and `UnionAttribute` (https://github.com/dotnet/runtime/pull/127001)
- [ ] Review public APIs
- [ ] It would also be good to test the `MaybeNull` postcondition through the conversion as well.
- [ ] WhitespaceNormalizer
- [ ] Named parameters, e.g. `union MyUnion(string str);`
- [ ] Parameter modifiers, attributes, and default values
- [ ] Redundant Union attribute `[Union] union MyUnion(string str);`
- [ ] Public API: confirm desired shape of declaration syntax
#### Inheritance
- [ ] Subtype of a class union is not a union
- [ ] Type parameter constrained to union type is not a union
- [ ] Union case types are not inherited from the base type
- [ ] Certain inherited union members can be used by pattern matching, etc. (TODO confirm which. `TryGetValue`/`HasValue`/`Value`?)
- [ ] Not implemented: base interfaces of `IUnionMembers` can contribute members

#### Case types
- [ ] `union` declaration with no case types (error)
- [ ] `union` declaration with duplicate case types (error)
- [x] `union` declaration parameter list permits only type syntax (no attributes, default values, etc.) (`UnionDeclaration_33`)
- [ ] `union MyUnion(MyUnion);` (union with itself as a case type)

#### Creation members
Includes constructor or static interface Create method. In general, members with bad signatures are not used.
- [ ] Custom union with no creation members (empty case type list) error per [unions.md#union-creation-members](https://github.com/dotnet/csharplang/blob/main/proposals/unions.md#union-creation-members) (`CaseTypes_01`, error missing?)
- [ ] Do not consider members with trailing defaults `Create(CaseType c, int i = 0);`
- [ ] Do not consider members with trailing params `Create(CaseType c, params int[] arr);`
- [ ] `Create(CaseType c = null)`
- [ ] `Create(params int[] arr)` (`int[]` is the case type)
- [ ] A best create overload must be found, e.g. `(MyUnion)null` is ambiguous when multiple disjunct reference case types

#### Conversions
- [ ] `var union = (MyClassUnion)null` yields a null value (standard conversion is better)
- [ ] User-defined conversions are better than union conversions
- [ ] Class union to interface conversion `[Union] class MyUnion : I1 { }`
- Implicit and explicit conversions `I1 i1 = (I1)union;`
- Test both when Union type itself implements `interface I1` or not
- [ ] `CaseType?` to `UnionType?` lifted conversion does not exist
- [ ] `CaseType` to `UnionType?` conversion does exist
- [ ] Collection expression element conversions: `(MyUnion[])[case1, case2, case3]`

#### Value property
- [ ] Value property is missing (error reported when matching on the union). [unions.md#value-property](https://github.com/dotnet/csharplang/blob/main/proposals/unions.md#value-property).

#### TryGetValue
- [ ] Behavior for `union is Base @base`, when multiple `TryGetValue(Derived)` are present.
- [ ] Bad signatures (accessibility, arity, parameter count, etc.). Generally the method is ignored in this case.
- [ ] Coverage both in union type and IUnionMembers type

#### HasValue
- [ ] `HasValue` property influences `Value` flow state
- [ ] Patterns use property in place of null tests where available
- [ ] Coverage both in union type and IUnionMembers type

#### Patterns
- [x] Recursive patterns `union is Type(P1) { Prop: P2 }` (`UnionMatching_08_Recursive_Property_01`)
- [x] List patterns `union is [1]` (`UnionMatching_25_List`)
- [x] Type narrowing such as `obj is MyUnion and CaseType` (`UnionMatching_18_BinaryAnd`)
- [x] Type narrowing of nested unions: `obj is MyUnion1 and MyCaseTypeUnion and MyInnerCaseType` (`UnionMatching_20_BinaryAnd`)
- [x] Union matching is not used for `_` and `var x` patterns (`UnionMatching_02_*`)
- [ ] `union as CaseType` does not access the `union.Value`
- [ ] `union is CaseType` ("classic" is-type expression) checks the `union.Value`
- [ ] Test behavior of an exhaustive union pattern, when the input union is "malformed", such that its Value is not one of the case types or null. (Expect to throw a SwitchExpressionException in the "unreachable" branch.)

#### `union U(int) { ... }` member restrictions
- [x] Single-parameter constructors are blocked
- [x] Explicitly declared constructors must chain
- [x] Fields are not permitted (incl auto-props, field-like events)

#### Case type restrictions
- [ ] Case types cannot be ref structs
- [ ] Case types cannot be pointers
- [x] Case types cannot be restricted types (`UnionDeclaration_15`)

#### Ref safety, unsafe, etc
- [x] `[Union] ref struct RS;` (`UserDefinedCast_RefStruct_Explicit`)
- [ ] `var ptr = &myUnion;` for union declarations (expect ERR_ManagedAddr)
- [ ] Safe-context of expressions using union conversions

#### Interop
- [ ] VB consumes unions as if they are ordinary class/struct declarations
- [ ] VB: block use of `` (declaring unions)?
- [ ] Ping F# as FYI

#### Misc
- [x] Missing `UnionAttribute` or `IUnion` types (not synthesized)
- [x] Expression trees: block union conversions
- [ ] Expression trees: block `union is CaseType`
- [ ] `` XML docs on `union(CaseType);` declarations?
- [ ] Parameter names of synthesized constructors: `union U(int); new U(value: 42);`
- [ ] `partial union U(int);` - works similarly as a partial struct
- [ ] `struct union { }` now blocked?

# Productivity
## Union declarations
- [x] completion (keyword `union`, type list, union members)
- [x] classfication
- [x] FAR
- [x] NavBar, FindSymbols
- [x] formatting
- [ ] disable GenerateConstructor? Start with `var x = new MyUnion(""); union MyUnion(int) { }`
- [ ] UpgradeProject
- [ ] offer to upgrade from union type?
## Union types
- [ ] Quick Info: do we want to display a list of possible types in the union?
- [ ] Suggest case types in `union switch { $$ => ... }` context?

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.