Hi Dart folks -
We (the Dart team) are preparing to land a breaking change for Dart 2 related to callable classes. Below is a summary of the changes and how they might impact you. Feel free to follow up here or with me directly with questions.
What is changing?
In Dart 1, a class with a call method is a subtype of a function type with the same signature.
```dart
// Dart 1
class C {
void call(int x) {}
}
typedef void F(int i);
void main() {
C c = new C<int>();
F f = c; // OK
f as C; // OK, cast succeeds
c(3); // OK
print(f is C); // OK, prints true.
F f2 = c;
print(identical(f, c)); // OK, prints true
print(f == c); // OK, prints true
print(identical(f, f2)); // OK, prints true print(f == f2); // OK, prints true
}
```
In Dart 2, we will continue to allow objects with call methods to be called, and to be assigned to variables of function type. However, the assignment of an object with a call method to a function type will now be treated as an implicit closurization of the `.call` method of the object. This changes the behavior of the code snippet above as follows:
```dart
// Dart 2
class C {
void call(int x) {}
}
typedef void F(int i);
void main() {
C c = new C<int>();
F f = c; // OK, treated as F f = c.call;
f as C; // Cast fails
c(3); // OK
print(f is C); // OK, prints false.
F f2 = c;
print(identical(f, c)); // OK, prints false
print(f == c); // OK, prints false
print(identical(f, f2)); // OK, prints true or false print(f == f2); // OK, prints true
}
```
What will break?
Most code that uses call methods is unaffected by this, and will silently continue to work.
Code that passes off callable objects as functions and then casts them back to the original class type will no longer work. This is rare, but code such as this can be refactored either to use the Expando class to attach the required state to an actual function, or to explicitly manage state using Maps from the function to associated state.
Code that relies on a callable object being identical to the result of implicitly casting the callable object to a function type will no longer work. This is rare, but code that relies on this will need to be refactored.
How do I know if this has broken me?
The most likely error that you will likely see as a result of this change is a runtime cast failure, with a message that looks like this:
Type '(int) => int' is not a subtype of type 'CallableClass'
This error is indicating that a function typed value is being explicitly or implicitly cast to `CallableClass`, where `CallableClass` is some class with a `call` method. Previously, the value being cast would have been an instance of `CallableClass`, and so the cast would have succeeded. After this change, the value being cast is the closurization of the `.call` method from `CallableClass`, and cannot be cast back to the original object.
Why is this change being made?
Callable classes cause complexity in the type system (e.g.
https://github.com/dart-lang/sdk/issues/29791), and also cause substantial implementation complexity. Making uses of callable classes as function types behave as a closurization of the call method supports the vast majority of use cases that we have found, at a tiny fraction of the implementation complexity.
I will update here when this change lands. Please reach out to me here or offline with any concerns, and/or with help resolving any issues after this change has landed.
thanks,
-leaf