dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.22k stars 1.57k forks source link

Confusing error message #35731

Closed kika closed 5 years ago

kika commented 5 years ago

Dart VM version: 2.1.1-dev.0.1.flutter-2cb346bd0c (Tue Jan 8 15:52:54 2019 +0000) on "macos_x64" on MacOS 10.13.6

I fail to comprehend a simple error message from Dart: (BuildContext, String) => ListTile' is not a subtype of type '(BuildContext, dynamic) => Widget

How this is true? String is the subtype of dynamic, everything is, right? dynamic is a subtype and a supertype at the same time. ListTile is the subtype of Widget, what else? How come this message could be true?

Context: https://github.com/AbdulRahmanAlHamali/flutter_typeahead/issues/23

mraleph commented 5 years ago

Here is a good litmus test for subtyping: if A is a subtype of B then you can use A everywhere where you can use B. (see Liskov's Substitution Principle).

For example if Dog is a subtype of Animal you can use Dog anywhere where you can use Animal.

Now consider (BuildContext, String) => ListTile and (BuildContext, dynamic) => Widget: the first function only takes String as a parameter, while the other takes anything dynamic. Which means that you can't use the first function everywhere where the second function is used. Consider the code like this:

void Function(String) f;
void Function(dynamic) g;

g(10);  // valid 
f(10);  // invalid, which means f can't be used like g

Formally speaking function types are contravariant with respect to their parameter types and covariant with respect to return types.

kika commented 5 years ago

@mraleph Exactly. I can use String everywhere I can use dynamic. I can use ListTile everywhere I can use Widget, not the other way around (in other words, I can pass ListTile to a function which promises to operate on any Widget, but I can't pass any Widget to a function which promises to operate on ListTile only). Your example is correct but illustrates a different issue: you have three types there - int, String and dynamic. int and String are siblings in the ancestry tree of sub/super types and you can't use them interchangeably.

Which means that you can't use the first function everywhere where the second function is used.

God forbid, why?!? The second function promises to take any dynamic and produce a Widget. We give her a String and ask for a ListTile. We are talking about type signatures here, not implementations, correct? LSP has some wording around could be safely used which allows different interpretations, but this issue is clearly about typechecker. You can't technically write an implementation of a function that returns Widget in Flutter, because Widget is abstract. (well, I believe, don't hold me accountable for this).

kika commented 5 years ago

https://en.wikipedia.org/wiki/Subtyping

https://en.wikipedia.org/wiki/Subtyping#/media/File:Inheritance.svg

(I cringe at the word "inheritance" in the title, but it is what it is).

I'm saying that ListTile is a duck and Widget is a bird. You say it's the other way around. Someone help me :-)

kmillikin commented 5 years ago

Which means that you can't use the first function everywhere where the second function is used.

God forbid, why?!? The second function promises to take any dynamic and produce a Widget. We give her a String and ask for a ListTile.

You're trying to use the second function where the first is used, which is the opposite. (That doesn't work either, because you expect a ListTile and you're getting a Widget instead.)

The reason you can't use the first function everywhere that the second function is used is: the first function promises to take any String and produce a ListTile. You give her a dynamic and oops.

mraleph commented 5 years ago

@kika

let me rewrite my example to make it clear:

void useSomeFunction(void Function(dynamic) g) {
  g(10);
  g(Cat());
  g(Dog());
  g(SpaceShip());
}

var f = (String value) { /* I can only handle String inputs */ };

useSomeFunction(f);  // Invalid. 
// Can't pass a function that can only deal with String inputs
// somewhere where a function that can deal with *any* inputs
// is expected.
kika commented 5 years ago

@mraleph @kmillikin Thanks! Indeed A ≤ B doesn't mean that f(A) ≤ f(B), but I believe that the error message could be enhanced. In its current form, it will come back biting many times, especially given the more "popular" error message List<dynamic> is not a subtype of List<Whatever> when you forget to use List.from(...).