Skip to content

Let a wildcard as an actual type argument indicate a request for inference #3963

Open
@eernstg

Description

@eernstg

This is a proposal for supporting expressions like MyClass<int, _>('Hello'), where _ is used to indicate that the corresponding type parameter should be obtained from type inference. This allows us to specify some type arguments whose value would otherwise not fit the needs, and omit other type arguments whose inferred value is as desired. For example:

class A<X, Y> {
  final X x;
  final _ys = <Y>[];
  A(x);
  void add(X Function(Y) g) => _ys.add(g(x));
}

// Assume that we want to create an `A<int, String>`.
void main() {
  // We can specify all type arguments, ..
  var a1 = A<int, String>(42); // .. but `int` is redundant.

  // We can infer all type arguments, ..
  var a2 = A(42); // .. but `Y` is now `dynamic`.

  // With this proposal, we get the best of two worlds.
  var a3 = A<_, String>(42);
}

We could allow a wildcard type argument that occurs as the last element of the actual type argument list to stand for multiple type arguments. For example, this would allow us to use C<T1, _> to stand for C<T1, T2, T3> where both T2 and T3 are inferred.

There should not be a large amount of expressive power in this feature, but it does take a few steps to emulate it in the current language.

We can get the same effect today if we're willing to create a type alias for each choice of fixed type arguments, and if those type arguments can be denoted globally:

// Same `class A`.

typedef AHelper<X> = A<X, String>;

void main() {
  // Emulate `A<_, String>(42)`.
  var a4 = AHelper(42); // Creates an `A<int, String>` and infers that type for `a4`.
}

We can also use a function to support arbitrary choices of "fixed" type arguments:

// Same `class A`.

// Create a context where we can denote a type which isn't denotable globally.
void foo<Z>() {
  // Emulate `A<_, Z>(42)`.
  // Note that we can't declare `typedef AHelper<X> = A<X, Z>;`
  // because type aliases are global.

  // We can still specify all type arguments, brute force.
  var a5 = A<int, Z>(42);

  // However, we want to avoid specifying `int` because it can be inferred. So we
  // use a local function that has the choice of `Z` as 2nd type argument baked in.
  A<V, Z> local<V>(V v) => A(v);
  var a6 = local(42);
}

void main() => foo<String>();

It is a new feature to include a special (wildcard-ish) treatment of _ when it is used as an actual type argument. It is also (slightly) breaking because it is possible today to declare some entities whose name is _ (e.g., top-level variables or, indeed, type parameters).

However, I think it's more helpful to allow developers to request this kind of partial type inference by means of _ than it is to preserve the ability to use a type whose name is _ as an actual type argument.

In the end, it would actually be possible to create a type alias to provide access to such types under a different name:

class _ {}
typedef AMuchBetterName = _;
List<AMuchBetterName> list = []; // Can't use `List<_>`, but this is better, anyway.

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureProposed language feature that solves one or more problemstype-inferenceType inference, issues or improvements

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions