Skip to content

Function internal parameter type override. #1311

Open
@lrhn

Description

@lrhn

In short, a parameter can have an added as type which changes the type of the local variable introduce by a parameter internally in the function, but doesn't change the declared type of the parameter in the function parameter.
Example:

iterableOfObject.forEach((x as String) => ...);
void someFunction({int optionalParameter as int? = 0}) => ...;

The as type goes after the identifier, or after the end ) for function-style parameters, and before default values for optional parameters.

The as type type should be related to the declared (or inferred) type, either a subtype or a supertype.

If the as type type is a subtype of the declared type, it adds an run-time down-cast of the argument to the as type type on function entry (in declaration order if there is more than one as for the same function), just as for covariant functions. The parameter's local variable's declared type is the as type type inside the body of the function.

This can be used as a solution to #477. Example:

var map = Map<int, String>.fromIterable(iterableOfInts, key: (x as int) => x, value: (x) => "$x");

(Because we don't have generic constructors yet, the type the key function must be K Function(Object), even if you know the iterable's element type).

If the as type type is a supertype of the declared type, then there is an implicit up-cast (meaning nothing) and the parameter variable simply has the as type type as its declared type.

In both situations, an optional parameter can have any default value which is a subtype of the as type type, not the declared type.
That allows a function to have an optional parameter which is not null, but which can be omitted, even when it's not possible to provide a valid default value for the parameter type. Example:

foo({int x as int? = null}) => ...

won't leak in the externally visible type that null is used internally to represent an absent parameter, and won't allow foo(x: null).

Allowing default values outside of the declared parameter type makes it detectable whether the parameter was passed or not. It's not possible to directly pass the default value, so the presence of the default value implies that the original function invocation omitted the argument for that optional parameter.
Being able to distinguish whether an argument was passed or not has historically caused problems for forwarding functions.
Consider a function foo({int x, int y}), and a function bar({int x, int y}) which wants to forward its invocation to foo.
Currently foo would need to have default values for each parameter, then bar could use the same default values and call foo(x: x, y: y) directly.
If foo could choose a default value that cannot be passed, say null, then bar would need to be written as:

bar({int x as int? = null, int y as int? = null}) =>
  x == null 
    ? y == null ? foo() : foo(y: y)
    : y == null ? foo(x: x) : foo(x: x, y: y);

This affects our own generated "noSuchMethod-forwarders", mixin application constructor forwarders, and other forwarding stubs, and it affects user code needing to forward parameters.
We might want to consider adding a way to conditionally pass an argument, say => foo(x: if (x != null) x, y: if (y != null) y), but that probably needs more design work if it also has to apply to positional arguments.

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureProposed language feature that solves one or more problems

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions