Description
Cf. #2269 (comment).
Consider the following example (a variant of the example in the issue mentioned above):
class C {
void foo([int x]);
noSuchMethod(i) => print(i.positionalArguments[0]);
}
void main() => C().foo();
This program has no compile-time errors (that's fine, none were expected), but the run-time behavior depends on the configuration: dart
runs the program and prints 'null', but DartPad and dart compile js
generate code where the execution stops before the print
expression because null is not a type correct actual argument when the parameter has type int
.
First, please make a clear distinction: C.foo.x
has no default value. If the declared type had been int?
then it would have had the default value null. If it had been declared with an explicit default value d
then it would have had the default value d
. In other words, an implicit null is just as much a default value as an explicitly declared default value, but it is possible for an optional parameter (of an abstract declaration) to have no default value at all.
I'd recommend that we specify a dynamic error as follows:
Assume that a class C
has an implicitly induced noSuchMethod forwarder D for a method named m
(because it is concrete and has a non-trivial noSuchMethod
implementation). Such noSuchMethod forwarders can be inherited, too, but we only need to specify the case where the forwarder is introduced. We could also have a noSuchMethod thrower, but they are not relevant for this issue.
Assume that D is invoked, and no actual argument is provided for a given optional formal parameter p
(which could be positional or named). A dynamic error will then occur in the case where the default value of p
is underspecified:
p
has no default value becauseC
contains an abstract declaration ofm
where no default value is specified forp
, orm
is in the interface ofC
because one or more direct superinterfaces have a member namedm
, and the most specific underlying declaration does not declare a default value forp
for any of them.p
has an ambiguous default value because multiple direct superinterfaces ofC
have a member namedm
whose most specific underlying declaration(s) have multiple non-identical default values forp
.
The default value can always be made available and unambiguous by putting an abstract declaration of m
into C
, specifying the desired default value. This implies, for example, that it is possible to resolve missing or ambiguous default values in superinterfaces without writing an actual method implementation. This could be useful in the declaration of a mock class.
This approach could be described as "fail late". We could also make it a compile-time error in every case where a noSuchmethod forwarder is being implicitly induced, and one or more optional parameters do not have a well-defined default value; that could be described as "fail early".
In both cases the change would be at least somewhat breaking: The current behavior in the VM allows noSuchMethod
to be invoked with null as an actual argument, even in cases where that would be a soundness violation in an explicit declaration of the forwarded method, but with this change it would become a dynamic error, or even a compile-time error.
With an ambiguous value, the VM currently makes a choice (perhaps using the value from the textually first superinterface that specifies a default value), which may or may not be the "right" choice. It is again a breaking change to start raising a dynamic error in the ambiguous case.
@dart-lang/language-team, WDYT? Do we need to change the current behavior? Fail early or late?