Description
Currently an assignment from a "callable class type" (an interface type where the interface has a call
method) to a function type or the type Function
will perform an implicit call
method tearoff.
That is:
class MyClass {
int call() => 42;
}
means that Function f = MyClass();
will be allowed, even if MyClass
is not a subtype of Function
, and will be implicitly converted to/treated as Function f = MyClass.call;
.
Whether to do the implicit tear-off depends on the context type where the expression occurs, which means that we have an expression which changes its type, invisibly, depending on an, also invisible, context type. It's hard to read, and if something goes wrong, it's very hard to see where.
This breaking change removes this implicit tear-off behavior, and requires you to write the .call
explicitly to get the current behavior.
Historically, prior to Dart 2.0, such callable class types were subtypes of the function type of their call
method. The Dart 1 type system was unsound in many ways, and this particular subtyping could not be included soundly in the Dart 2 type system. Instead an implicit coercion was introduced, which made some of the old code keep working, but not all of it. Code that relied on the actual callable class object being stored in the variable would no longer work. The torn-off call
method was stored instead.
The current code still looks like it stores the object, which is deceptive and potentially error-prone.
And it does so based on a potentially invisible context type, which makes it intractable to verify the code by visual inspection.
There is no syntax suggesting that something special is going on in this assignment. There is no warning if it stops happening for some reason, perhaps because the context type changes to dynamic
.
Because of that, it's considered better for readability and safety to force the tear-off to be explicit.
Also, with the patterns proposal, there will be more ways to do assignment, with more distance between the variable and the initializing expression, which makes it even easier to make mistakes and overlook the implicit tear-off.
There are no plans to change the behavior when calling such an object. You can still do:
var myFunctionLike = MyClass();
var myInt = myFunctionLike();
without having to write myFunctionLike.call()
.
There is syntax here suggesting exactly what's happening (something is getting called), and no expression which changes type because of an invisible coercion, and almost no dependency on the context type (unless the call
method is generic, then type inference may use the context type to infer type arguments as usual).