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

Generic function type mismatch: type '(Subtype) => ReturnType' is not a subtype of type '((Supertype) => ReturnType)?' #55286

Closed
maRci002 opened this issue Mar 23, 2024 · 4 comments

Comments

@maRci002
Copy link

class A {}

class A2 extends A {}

class B<T extends A> {
  const B({this.method});

  final num Function(T)? method;
}

void main() {
  final List<B<A>> list = [
    B<A2>(method: (A2 data) => 1),
    const B(),
  ];

  final mention = list[0];
  print(mention);

  var method = mention.method;
  print(method);
}

Output:

Instance of 'B<A2>'
Unhandled exception:
type '(A2) => int' is not a subtype of type '((A) => num)?'
#0      main (package:mark_content/asd.dart:20:24)
#1      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:297:19)
#2      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
@maRci002
Copy link
Author

I think the problem is that the mention is of type B<A>, which is actually a B<A2> type. Therefore, I get an error since I won't be able to pass an instance of A, but rather an instance of A2.

@lrhn
Copy link
Member

lrhn commented Mar 24, 2024

Correct. The occurrence of T in

  final num Function(T)? method;

is contravariant, while the T in the type itself is (unsafely) covariant.

Because of that, the code that reads the property mention.method, where mention has static type B<A>, and the read expression therefore static type num Function(A), must be prepared for the value to be, fx, a num Function(A2), which is-not-a num Function(A).
To preserve soundness, the guarantee that the value of an expression satisfies the static type, the compiler inserts a type check that throws if the value isn't sound.

Basically it makes the read expression into (mention.method as num Function(A)).

Which then fails at runtime, as intended.

If it hadn't thrown there, the next statement could've been method(A()), which wouldn't be sound.

If you change the method getter to a method:

final num Function(T) _method;
num method(T value) => _method(value);

then the reading becomes type safe, because the reader is now using the same type for T, but calling the method may throw at runtime.

There is just no way to allow an actual num Function(A2) to be successfully called with a A() value.

@maRci002
Copy link
Author

Thank you for the clarification. Anyway, I did something similar with manual casting:

final num Function(T) _method;
num method(A value) => _method(value as T);

But your solution is better.

@eernstg
Copy link
Member

eernstg commented Mar 25, 2024

See dart-lang/language#297 for some background info about the treatment of occurrences of type variables in positions that disagree with their variance.

See dart-lang/language#524 for a proposal about statically checked variance (covariance, contravariance, invariance). If we get that then you can use this:

class B<in T extends A> {
  const B({this.method});

  final num Function(T)? method;
}

.. and then B<A2> will be a supertype of B<A>. You can vote for dart-lang/language#524 if you wish to support the addition of a statically checked kind of variance to Dart.

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