Skip to content

Latest commit

 

History

History
71 lines (45 loc) · 3.45 KB

File metadata and controls

71 lines (45 loc) · 3.45 KB

Target-typed inference for type patterns

Champion issue: dotnet#9630

*This proposal builds on the target-typed generic type inference proposal.

Summary

Generic type inference is extended to type patterns, which may omit a type argument list when it can be inferred from the pattern input value. For instance, given a declaration Option<int> intOption, instead of:

if (intOption is Some<int> some) ...

You can simply write:

if (intOption is Some some) ... // 'Some<int>' inferred from the type of 'intOption'

Motivation

Type patterns can get unwieldy when the types are generic, which seems especially grating when the information to infer the type arguments is already available in context.

For closed hierarchies and unions in particular, there are already rules in place that ensure that type arguments for a case type depend functionally on those of the closed class or union. This means that those type arguments are almost guaranteed to be inferrable when the input value is of a closed class or union type, and the type pattern is for a case type.

Detailed specification

The proposal is specified by treating the type pattern "as if" it were a generic method, and the pattern application to the incoming value "as if" it were an invocation of that generic method with a target type of the incoming value.

In a type pattern T ... where T has no type arguments, if a non-generic T does not exist, or is not allowed in a pattern (e.g. it is static), or is not pattern compatible with the input type I, then type inference is attempted:

For each generic type T<X₁...Xᵥ> with the same type name T, generic type inference is performed as if the type pattern were a generic method with the same type parameter list as T<X₁...Xᵥ>, with an empty parameter list, and with T<X₁...Xᵥ> as its return type:

T<X₁...Xᵥ> M<X₁...Xᵥ>()

and as if the pattern application were an invocation with no type arguments, with an empty argument list and with I as a target type:

I i = M()

(Where the names i and M are otherwise invisible and not in conflict with any other names in scope.)

For instance, in the following example:

public record None();
public record Some<T>(T value);
public union Option<T>(None, Some<T>);

void M(Option<int> intOption) => intOption switch
{
    None => ...,
    Some some => ..., // 'Some<int>' inferred for 'some'
}

Type inference proceeds as if with this method and invocation:

Some<T> M<T>();

Option<int> i = M();

Which leads to int being inferred as the type argument corresponding to T.

If type inference succeeds and the inferred type arguments satisfy their constraints, then the type T<X₁...Xᵥ> is a candidate. If exactly one such candidate type is found, then that is the one inferred for use in the type pattern. Otherwise, inference fails and an error occurs. The type in the pattern must then be specified in full.