dart-lang / dart-lang/language

Current "covariance by default" paradigm is not respected by the compiler

Open
#2,322 5 comments 1 reaction 0 assignees View on GitHub
request
Dominant language
TeX
Stars
2.9k
Forks
239
Avg merge
2d 18h
Merged PRs (30d)
14

Description

After some digging around old issues, I can see there has been some discussion in the past about providing a more robust type system to Dart by including specific covariant/contravariant type definitions as provided by other languages (java, kotlin, etc).

That being said, it is my understanding that, currently, the language design is to consider everything to be "covariant by default":

> As of Dart 2.4 or earlier, every type variable declared for a generic class is considered covariant

(from [this great issue from 2019](https://github.com/dart-lang/language/issues/524)).

Which is not, as discussed, sound (nor safe) -- and I would love to see that addressed. But I am not sure the status of these proposals, as there are many open, old issues from the community about it. And while I would much prefer that to be the answer to this, I believe that even when considering the current rule, there is an issue with the compiler implementation (if I am not misinterpreting these statements).

Assuming the "covariant by default" rule, a `List` should _be_ a `List`, for all intents and purposes:

```dart
void take(List ns) {}

void main() {
take([]); // ok
take([]); // ok
}
```

However, this does not work when the generics are a bit more involved. See an example below: ([dartpad](https://dartpad.dev/?id=5742674b550cfd66beee51b67772e444)):

```dart
void take>(List list) {}

void main() {
take([]); // ok -- T = num
take([]); // not ok -- does no compile, it is trying to resolve T=double instead of the also valid T=num
take([] as List); // ok, teach the compiler. but warns and wants to "auto-fix"
}
```

Here, my function generics satisfy `List`. But for some reason, it does not compile for `List`. Of course `double` itself does not satisfy `T extends Comparable`, but `num` does. I believe the _current_ "covariance by default" rule is being violated here, as my `List` should be treated as a `List` to satisfy the type constraint.

To make it even more weird, see how I can cast the `List` to `List` and then it totally works! But then the compiler correctly points out that since dart is "covariant by default" the cast is unnecessary, and the IDE will auto-fix to remove the cast. This removal actually causes the code to break.

The way I am understanding this, this is a violation of the current rule, but as pointed out in many issues, the current rule isn't good to begin with. So my question/proposal is:

1. am I missing something on my analysis? is there current any way to accomplish this?
2. is there any ongoing plan to add something akin to Java's wildcard + `T super U` syntax, or explicit covariant/contravariant modifiers like kotlin's `in`, `out`, `inout`?

Because if not, I believe this should be treated as a bug as `take([])` should work with the current ruling, as far as I understand it. And that might be a much quicker fix than to get the actual spec sorted out. Also, having the compiler warning/auto-fix generate non-compiling code is odd.

The reason I am asking this is that this not an abstract, far-off, edge case that I machinated; having a function that takes a list whose elements can be compared seems to be like a very common use case (e.g. sorting, finding min, max, etc). And not being able to pass a list of `double` or `int` because of how they implement `Comaprable` limits our design choices.

If there are approved plans to solve this by the root cause though -- and make the type system more sound--, I am happy to wait for that solution instead, as that would allow for even more cool & safe code :)

Contributor guide

Open the contributing guide

Research direction

Reproduce the supplied DartPad example first and compare its generic type inference and covariance behavior with the current Dart language rules. No repository file or test is named; done requires deciding whether the compiler and auto-fix violate the existing rules or whether a language-design change is needed.

Written by the indexing model from the issue text.

Assessment

Tech stack
dart
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.