dart-lang / dart-lang/language
[breaking change] Remove implicit tearoff of `call` methods
- Dominant language
- TeX
- Stars
- 2.9k
- Forks
- 239
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 14
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:
```dart
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:
```dart
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).
Contributor guide
Assessment
This issue has not been assessed yet.