Back to Csharplang

C# Language Design Meeting for August 27th, 2025

meetings/2025/LDM-2025-08-27.md

latest6.5 KB
Original Source

C# Language Design Meeting for August 27th, 2025

Agenda

Quote of the Day

  • "Boiled frogs taste delicious." "Not if you're the frog."

Discussion

Type inference in patterns

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:

cs
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.

Conclusion

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.

Type value conversion

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:

  1. A conversion, as specified right now. The main disadvantage of this is that conversions generally can't happen in the middle of a dotted expression, except on extension receivers. The advantage, though, is that we don't need to solve a new Color Color problem, since we wouldn't be introducing a new spot where type and instance confusion can occur.
  2. Allowing users to define a property with the same name as a nested type. This is the real root of the specific problem that this proposal is solving: we want to allow things like 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.
  3. Introduce a general operator for this (not a conversion) that can occur in the middle of dotted expressions. This would also require us to solve the general 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.

Conclusion

We'll work on specifying option 3 more completely.

Union syntax

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:

  1. The original: union Pet(Cat, Dog, Bird);
  2. Using bars: union Pet(Cat | Dog | Bird);
  3. Using or: union Pet(Cat or Dog or Bird);
  4. Using enum bodies:
cs
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.