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 type information gets lost when directly passed to a function #2314

Closed
Dimibe opened this issue Jun 25, 2022 · 2 comments
Closed

Generic type information gets lost when directly passed to a function #2314

Dimibe opened this issue Jun 25, 2022 · 2 comments

Comments

@Dimibe
Copy link

Dimibe commented Jun 25, 2022

Hi,
I am encountering the following behavior and I am wondering if this is correct.

I have a function that expects an Iterable<Object>.
Now, when I assign a List without a generic type to a variable declared with var, the type is automatically interfered as List<int>.
If I subsequently call the method with this variable, the parameter is also recognized as List<int>.
In the second case I skip the variable declaration and pass the list directly to the function.
In this case, the type is correctly recognized as a list (instead of iterable), but the generic type is not interfered as int.
So when passing a list directly, the generic parameter must be specified in the function call for the function to interfere it correctly.

Example:

  void test(Iterable<Object> list) {
    print(list.runtimeType);
    print(list is List<int>);
  }

  var list = [1, 2, 3];
  test(list); // prints 'List<int>' and 'true'
  test([1, 2, 3]); // prints 'List<Object>' and 'false'
  test(<int>[1, 2, 3]); // prints 'List<int>' and 'true'

In my opinion it shouldn't make a difference whether I assign the list to a variable declared with var before the function call or pass it directly into the function.

Can you please explain to me why the code behaves the way it does and if the behavior is expected? Thanks in advance.

Tested with Dart 2.17.1

@Levi-Lesches
Copy link

Levi-Lesches commented Jun 26, 2022

I know there was a big change that landed recently that may not have shipped yet (#731), so I may be wrong, but here's my guess:

When you declare list, Dart is trying to figure out the type for you. It doesn't have context, so it's just trying to find the most helpful (usually, specific) type, which gives List<int>. When you make a list as an argument to test, Dart again tries to figure out its type, this time with the context that you want an Iterable<Object>. The [] syntax means List, so Dart knows it's not just any Iterable, but it has no reason to check any further. In other words, test only expects an Iterable<Object>, and so it refines List to List<Object>. That's enough to satisfy your constraints, so it stops there. Note that List, ie List<dynamic>, isn't enough since dynamic is nullable and Object isn't.

I guess another way of looking at it is you created this list just to pass it to test. If you wanted to use it otherwise, you'd make it a regular variable (either with a type or var). The only other way to access that list ever again would be if test returns it, in which case it would denote a return type. Whether you use an initialized variable or the return type, Dart can use that information to give your list a more specific type. But since the list isn't used again, it doesn't need a more specific type.

@lrhn
Copy link
Member

lrhn commented Jun 26, 2022

This is working as intended.

As @Levi-Lesches says:
When dart sees the expression [1, 2, 3], it tries to infer the type argument, effectively solving context(<X>[1, 2, 3]) for X.

To do that, it looks both at which type the context expects, and what types the arguments have (elements here, since this is a list, not a function call, but the principle is the same).

The type inference algorithm prefers the context type. If there is one, it wins. Only if the context type is not sufficient to infer all the type arguments will the algorithm look at the arguments.

So, in this case: test([1, 2, 3]) has [1, 2, 3] in a context expecting Iterable<Object>. A List<Object> is an Itearble<Object>, so we have a solution, and the program becomes as if it had been written test(<Object>[1, 2, 3]).

In the case of <int>[1, 2, 3], there is no inference, you get what you ask for.

In the case var list = [1, 2, 3]; test(list);, there is no context for [1, 2, 3], so it looks at the elements and infers <int>[1, 2, 3].

So, working as intended.

@lrhn lrhn closed this as completed Jun 26, 2022
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