Skip to content

Inconsistent generic type inference when calling a function inline vs. assigning to a variable #4312

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

Open
Diaga opened this issue Apr 1, 2025 · 3 comments
Labels
type-inference Type inference, issues or improvements

Comments

@Diaga
Copy link

Diaga commented Apr 1, 2025

class Event {}

class EventSubscription<T extends Event> {}

typedef EventHandler<T extends Event> = void Function(T);

EventSubscription<T>? on<T extends Event>(EventHandler<T> handler) {
  return null;
}

void add(EventSubscription<Event>? event) {}

class ChildEvent extends Event {}

void main() {
  // Inline call errors with:
  // "The argument type 'void Function(ChildEvent)' can't be assigned to the parameter type 'void Function(Event)'"
  add(on((ChildEvent event) {}));

  // But if I split it up:
  final x = on((ChildEvent event) {});
  add(x); // OK
}

Observed behavior:

  • When I call add(on(...)) inline with a ChildEvent handler, I get a type error in Dart: “The argument type 'void Function(ChildEvent)' can’t be assigned to the parameter type 'void Function(Event)'.”

  • However, if I store the result of on((ChildEvent event) {}) in a local variable and then pass that variable to add, it compiles without error.

Expected behavior:

  • I would expect consistent type checking. Either both ways should fail if EventSubscription isn’t assignable to EventSubscription, or both ways should succeed if an implicit cast is valid. It’s confusing that Dart’s type system errors on the inline approach.

Environment:

  • Dartpad: Based on Dart SDK 3.7.2 and Flutter SDK 3.29.2
@rrousselGit
Copy link

Looks like a type inference issue. It could be a case of "what has the priority in determining the generic type?"

I'm not on my work computer, but I think it infers to:

  add<Event>(on<Event>((ChildEvent event) {}));

Instead of on((ChildEvent event) {}) you probably want on<ChildEvent>((event) {})

@lrhn
Copy link
Member

lrhn commented Apr 2, 2025

Looks correct.

When given as argument to add, the on call has a context type of

EventSubscription<Event>?

That guides type inference making on return an EventSubscription<Event> and therefore binds T to Event.
Thats the least restrictive binding that can work in this context, so it should be fine.

When assigned to an untyped variable, type inference doesn't get a context to guide inference of T on the way down, so it continues inferring the arguments, and tries to infer a T from those, making it ChuldEvent.

The problem here is that T occurs contravariantly in the parameter type of on, so the "least restrictive" binding of T is actually the most restrictive for that parameter. That's why you get an error after downwards inference has decided to use Event for T.

@lrhn lrhn added the type-inference Type inference, issues or improvements label Apr 2, 2025
@FMorschel
Copy link

I think this is somewhat related to #3527

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-inference Type inference, issues or improvements
Projects
None yet
Development

No branches or pull requests

4 participants