linter
linter copied to clipboard
Hint and/or lint for changing `List.from` to `List.of`
Many uses of List.from can be changed to List.of.
The reasons to do so:
List.ofis nicer to use because the extra constraint helps type inference and editor suggestionsList.ofis more efficient- There is an education problem that since
List.ofis newer, there are lots of poor examples ofList.fromthat could have been migrated toList.of.
Examples that could be hints (i.e. no false positives):
- The explicit or inferred type argument
EinList<E>.from(iterable)matches the static type of the iterable argument - The explicit type argument
EinList<E>.from(iterable)is a supertype of the static type of the iterable argument
More complicated is where dynamic creeps in.
I have seen examples like:
void foo(Map<String, String> map) {
for (final key in List.from(map.keys)) {
print(key.isEmpty);
...
If we change List.from to List.of, the inferred type of key moves from dynamic to String. This would be helpful to the developer for completion suggestion, since key.isE• can now only complete to key.isEmpty, and not key.isEven.
We would want to suggest the replacement if the new inferred types do not lead to new or different errors or warnings or a different resolution of elements (e.g. a dynamic instance method call becoming a static extension method call).
Does the same advice extend to Set.of?
Currious : is List<E>.of(iterableOfE) better than iterableOfE.toList() ?
If you look at https://api.dart.dev/stable/2.8.4/dart-core/Iterable/toList.html you can see that the implementation relies on List.from. Should it be changed to use List.of ?
Does the same advice extend to
Set.of?
And Map.of?
The advice extends to Set.of and Map.of.
@a14n: Yes, we should change that List.from to List.of. That code dates back to Dart 1 before there was a List.of, and the proposed hint would help find it.
@a14n: Regarding iterableOfE, less clear cut, so I would not suggest a similar hint.
They are different in that the run time type value E for toList comes from the object, and for List<E>.of from the context of the call.
I would use List.of if I wanted to update an element of the list; for lists that are not modified, toList is fine.
Reasons to use iterableOfE.toList():
- It is more concise
- Plays well with type infererence
- It is in the right place when thinking of a chain like where-map-where-toList.
- It can be a custom implementation, bound to the receiver, which can be more efficient that
List.of. For this reason sometimes I wishList.ofwas implemented in terms oftoList, but we can't becausetoListis less trustworthy (see below).
Reasons to use List.of:
- You want to modify the list (e.g. assign a new element).
toListmight return a list with too narrow a type. - The compilers can do a more consistent good job with
List.ofbecause there is one implementation that is known to the compiler and the compiler knows the return type is one of the system lists, and how that depends ongrowable. - There are many implementations of
toList, and some of them might be confusing for the compilers- The return type of
toListhas unconstrained variance, so it can return a subtype, e.g.=> <Never>[]; - The compilers can't always tell the exact implementation type(s) returned by
toList, making handling of the returned list potentially polymorphic and less efficient.
- The return type of
We might also consider folding this advice into Effective Dart?
See: https://dart.dev/guides/language/effective-dart/usage#dont-use-listfrom-unless-you-intend-to-change-the-type-of-the-result
Isn't one of the main benefits of using .from that you can cast the types more precisely?
void parse(Map<String, dynamic> json) {
for (final MapEntry<String, dynamic> entry in json.entries) {
print("${entry.key}: ${entry.value}");
}
}
void main() {
final Map json= {"hello": 1, "world": true}; // typed as Map<dynamic, dynamic>
parse(Map<String, dynamic>.of(json)); // Error: Map<dynamic, dynamic> is not Map<String, dynamic>
parse(Map<String, dynamic>.from(json)); // okay
}
Seems like a duplicate of https://github.com/dart-lang/linter/issues/2066?
Well that was created later than this, and the discussion is here, so technically #2066 is a duplicate of this
Well that was created later than this
Huh? This issue seems to have been created in March 2021 (unless transferring the issue modified the timestamp?). #2066 was filed last year. =/
Oh sorry, I didn't see the years, just "March" and "April"
I'm seeing this issue in Flutter framework code - using from where we reallys hould use of because of extra type checking that is incurred by from.
And Queue.from => Queue.of.
@Levi-Lesches to answer your question, which holds with @rakudrama 's initial suggestion, is that we would not report a List.from if the explicit type argument E in List<E>.from(iterable) is not a supertype of F where iterable as an instance of Iterable is Iterable<F> .