meetings/2025/LDM-2025-08-27.md
Champion issue: https://github.com/dotnet/csharplang/issues/9630
Specification: https://github.com/dotnet/csharplang/blob/12e6f5b0d512d15d32c8e7ae95674bd070b2758f/proposals/inference-for-type-patterns.md
First up today, we looked at the next proposal in the list for type inference improvements. These proposals arise from
the general unions discussion but can also be useful outside of unions. In this case, we're looking at type inference
inside patterns. Unlike the previous two proposals, which were easier sells, LDM is more skeptical about this one. In
particular, type inference has more latitude for patterns than it does for constructor calls, since you can start with a
base type and narrow to a derived type. In constructors, the new must refer to something that either is the same type
as the target, or has a direct conversion to the target. In patterns, that isn't the case. You can have a pattern that
downcasts, upcasts, or checks a completely unrelated interface, which affords much more freedom. We also know of users who
will explicitly state types to serve as a null check in their patterns, even if that's an identity conversion. There are
mitigations though: if a user has a generic type in hand and they're pattern matching on it, we think it's unlikely that
they would upmatch to the non-generic base rather than to a more derived type. The main scenario we can think
of in this category would be something like:
IEnumerable<string> e = ...;
if (e is IList) { ... }
Under the current proposal, this would continue to match System.Collections.IList, and not infer
System.Collections.Generic.IList<string>, but should it? Would we consider breaking this behavior? There's also concern
that it may be surprising that a pattern, which is supposed to match against a type, might do type inference that isn't
immediately visible at the pattern site.
One potential option would be to always require some sigil or syntax to inform both the compiler and the user that inference
is happening and expected. For example, Type<>. This particular syntax didn't immediately resonate with everyone though;
a few LDT members preferred no syntax at all, and others felt that it might be confusing given that open types in typeof
require commas to differentiate how many omitted type parameters exist.
Another topic we briefly considered was whether to push inference further. An is Type check is effectively an as
combined with an immediate null check, and an as is just one form of cast. Should both as casts and hard casts also
support this? It seems reasonable that if option is Some would benefit, (Some)option would similarly benefit.
We came up with more questions than answers. This will need further investigation; we see value in the proposal, but we need to think about it more.
Champion issue: https://github.com/dotnet/csharplang/issues/8928
Document: https://github.com/dotnet/csharplang/blob/12e6f5b0d512d15d32c8e7ae95674bd070b2758f/meetings/working-groups/discriminated-unions/type-value-conversion.md
Next, we looked at yet another union-related proposal, this time on allowing type expressions to be directly converted to instances. While we think this is useful for singleton union cases, we also think it has applicability outside of unions.
We think there are at least three approaches we can take to this problem:
Color Color problem, since we wouldn't be introducing a new spot where type and instance confusion can occur.Option<T>.None as a value. If that was just a property with
the same name as the nested type, then it would work fine. That would give us a new instance of the Color Color
problem, though, as we'd need to determine, every time we see Option<T>.None, whether None refers to the property or
the type.Color Color problem, but it is more generally useful than option 2 for non-nested
singleton types.After discussion, we preferred option 3: it's the most generally useful and can solve this for both nested and non-nested types. There are still plenty of design decisions to make, but we like the direction.
We'll work on specifying option 3 more completely.
Champion issue: https://github.com/dotnet/csharplang/issues/9411
Union overview issue: https://github.com/dotnet/csharplang/issues/8928
Specification: https://github.com/dotnet/csharplang/blob/12e6f5b0d512d15d32c8e7ae95674bd070b2758f/proposals/nominal-type-unions.md
Finally, we took a brief look at our union syntax again. A few members of the LDM and some members of the community are not
satisfied with the current union Pet(Cat, Dog, Bird); syntax. Much of the current concern can be summarized as "it looks
like a primary constructor that implies order, which it's not." One thing we want to make sure of is that we're not
over-indexing on making new syntax stand out; is this a real consistency issue, or is this just an initial confusion that
will fade with usage? We brainstormed a few other syntax options:
union Pet(Cat, Dog, Bird);union Pet(Cat | Dog | Bird);union Pet(Cat or Dog or Bird);union Pet
{
Cat, Dog, Bird
}
Option 2 received little support. Option 3 provides a nice symmetry with how consumption in patterns will be structured. Option 4 has a nice symmetry with enums and potentially provides a single syntax we could use for defining members on both unions and enums. We didn't make a decision today, but we do think it's clear that there's enough here to warrant revisiting the syntax decision.