Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Callable classes loose their type when passed as function type #49683

Closed
renggli opened this issue Aug 17, 2022 · 4 comments
Closed

Callable classes loose their type when passed as function type #49683

renggli opened this issue Aug 17, 2022 · 4 comments

Comments

@renggli
Copy link

renggli commented Aug 17, 2022

Consider the following minimal test case:

import 'package:test/test.dart';

typedef FooFunction = void Function();

class FooObject /* implements FooFunction */ {
  void call() {}
}

String foo(FooFunction foo) {
  if (foo is FooObject) {
    return 'FooObject';
  } else {
    return 'FooFunction';
  }
}

void main() {
  test('test', () {
    expect(foo(() {}), 'FooFunction');
    expect(foo(FooObject()), 'FooObject');  // Returns `FooFunction`
  });
}

Surprisingly, when calling foo with an object, the object looses the FooObject type and magically becomes a FooFunction. I would expect the original object type to be preserved as if FooObject actually implemented FooFunction.

@eernstg
Copy link
Member

eernstg commented Aug 17, 2022

This is working as intended.

We used to have support for custom function objects: An instance of class C { void call() {}} would have type C and C would be a subtype of the function type of its call method, void Function().

However, that approach was too costly: It introduces infinite types like void Function(void Function(void Function(...))), and they never turned out to be of any practical value, and they make all invocations of first class functions much harder to optimize. So we dropped that feature, several years ago.

In order to reduce the amount of breakage to a minimum, we introduced a tiny bit of syntactic sugar: If an expression e has an interface type with a method named call, and e is used in a context where a function type or the type Function is expected, then e is transformed into e.call.

This means that your example is exactly the same thing as this:

import 'package:test/test.dart';

typedef FooFunction = void Function();

class FooObject {
  void call() {}
}

String foo(FooFunction foo) {
  if (foo is FooObject) {
    return 'FooObject';
  } else {
    return 'FooFunction';
  }
}

void main() {
  test('test', () {
    expect(foo(() {}), 'FooFunction');
    expect(foo(FooObject().call), 'FooObject');  // Returns `FooFunction`
  });
}

I'll close this issue because it is all working as intended.

@eernstg eernstg closed this as completed Aug 17, 2022
@mraleph
Copy link
Member

mraleph commented Aug 17, 2022

For context, original breaking change announcement from Dart 2 times: https://groups.google.com/a/dartlang.org/g/misc/c/hvt6jpiYxsE/m/ZQsrT67ZAQAJ

@renggli
Copy link
Author

renggli commented Aug 17, 2022

Thanks for the quick response!

For me this happened with new code and it is a surprising and (I think) inconsistent behavior with the rest of the Dart type system. Maybe it would make sense to deprecated the ability to implement call methods, or at least add a warning to the language tour?

@mraleph
Copy link
Member

mraleph commented Aug 17, 2022

Coincidentally this is considered here: dart-lang/language#2399

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants