Method group natural type improvements

Note

This article is a feature specification. The specification serves as the design document for the feature. It includes proposed specification changes, along with information needed during the design and development of the feature. These articles are published until the proposed spec changes are finalized and incorporated in the current ECMA specification.

There may be some discrepancies between the feature specification and the completed implementation. Those differences are captured in the pertinent language design meeting (LDM) notes.

You can learn more about the process for adopting feature speclets into the C# language standard in the article on the specifications.

Summary

This proposal refines the determination of the natural type of a method group in a few ways:

  1. Consider candidate methods scope-by-scope (instance methods first, then each scope subsequent scope of extension methods)
  2. Prune candidates that have no chance of succeeding, so they don't interfere with determining a unique signature:
    • Prune generic instance methods when no type arguments are provided (var x = M;)
    • Prune generic extension methods based on being able to reduce the extension and on constraints

Context on method group natural type

In C# 10, method groups gained a weak natural type.
That type is a "weak type" in that it only comes into play when the method group is not target-typed (ie. it plays no role in System.Action a = MethodGroup;).
That weak natural type allows scenarios like var x = MethodGroup;.

For reference: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md#natural-function-type

A method group has a natural type if all candidate methods in the method group have a common signature. (If the method group may include extension methods, the candidates include the containing type and all extension method scopes.)

In practice, this means that we:

  1. Construct the set of all candidate methods:
  • methods on the relevant type are in the set if they are static and the receiver is a type, or if they are non-static and the receiver is a value
  • extension methods (across all scopes) that can be reduced are in the set
  1. If the signatures of all the candidates do not match, then the method group doesn't have a natural type
  2. If the arity of the resulting signature doesn't match the number of provided type arguments, then the method group doesn't have a natural type
  3. Otherwise, the resulting signature is used as the natural type

Proposal

The principle is to go scope-by-scope and prune candidates that we know cannot succeed as early as possible (same principle used in overload resolution).

  1. For each scope, we construct the set of all candidate methods:
  • for the initial scope, methods on the relevant type with arity matching the provided type arguments and satisfying constraints with the provided type arguments are in the set if they are static and the receiver is a type, or if they are non-static and the receiver is a value
  • for subsequent scopes, extension methods in that scope that can be substituted with the provided type arguments and reduced using the value of the receiver while satisfying contstraints are in the set
  1. If we have no candidates in the given scope, proceed to the next scope.
  2. If the signatures of all the candidates do not match, then the method group doesn't have a natural type
  3. Otherwise, resulting signature is used as the natural type
  4. If the scopes are exhausted, then the method group doesn't have a natural type

Relates to scope-by-scope proposal: https://github.com/dotnet/csharplang/issues/7364 Relates to proposal to better handle generic extension methods: https://github.com/dotnet/roslyn/issues/69222