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