-
Notifications
You must be signed in to change notification settings - Fork 11
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
Collection: Major version, use 3.0 language features. #859
base: main
Are you sure you want to change the base?
Conversation
Keep `.whereNotNull`. It's deprecation is fairly new. Deprecate `IterableZip` in anticipation of getting `.zip` in the platform libraries.
Keep `.whereNotNull`. It's deprecation is fairly new. Deprecate `IterableZip` in anticipation of getting `.zip` in the platform libraries.
Starts using Dart 3.0 class modifiers. This is a breaking change, making some classes be `final` or `interface` which prevents behavior that was previously possible. Removes existing deprecated API, except for `whereNotNull`. Deprecates API that is available in platform libraries (`IterableZip` class). Changes behavior of `UnorderedIterableEquality`, `SetEquality` and `MapEquality` to assume the compared collections' notions of equality agree with the `Equality` object for elements/keys in the `UnorderedIterableEquality`/ `SetEquality`/`MapEquality` object. This should improve performance of the equality comparisons, which currently try to assume nothing, and therefore cannot use `.contains`/`.containsKey`/`[]` on one collection with keys/values from the other collection Exceptions: - `CanonicalizedMap` marked `@sealed` instead of `final`. Was extended in at least one place. Fix to that package sent.
PR Health
Breaking changes
|
Package | Change | Current Version | New Version | Needed Version | Looking good? |
---|---|---|---|---|---|
collection | Breaking | 1.19.1 | 1.20.0-2.0.0.wip | 2.0.0 Got "1.20.0-2.0.0.wip" expected >= "2.0.0" (breaking changes) |
This check can be disabled by tagging the PR with skip-breaking-check
.
Changelog Entry ✔️
Package | Changed Files |
---|
Changes to files need to be accounted for in their respective changelogs.
Coverage ⚠️
File | Coverage |
---|---|
pkgs/collection/lib/src/boollist.dart | 💚 100 % |
pkgs/collection/lib/src/canonicalized_map.dart | 💔 76 % ⬇️ 1 % |
pkgs/collection/lib/src/combined_wrappers/combined_iterable.dart | 💚 100 % |
pkgs/collection/lib/src/combined_wrappers/combined_iterator.dart | 💚 100 % |
pkgs/collection/lib/src/combined_wrappers/combined_list.dart | 💚 100 % |
pkgs/collection/lib/src/combined_wrappers/combined_map.dart | 💚 100 % |
pkgs/collection/lib/src/comparators.dart | 💚 99 % |
pkgs/collection/lib/src/empty_unmodifiable_set.dart | 💚 67 % ⬆️ 10 % |
pkgs/collection/lib/src/equality.dart | 💚 89 % |
pkgs/collection/lib/src/equality_map.dart | 💚 100 % |
pkgs/collection/lib/src/equality_set.dart | 💚 100 % |
pkgs/collection/lib/src/functions.dart | 💚 100 % |
pkgs/collection/lib/src/iterable_extensions.dart | 💚 100 % |
pkgs/collection/lib/src/iterable_zip.dart | 💚 100 % |
pkgs/collection/lib/src/priority_queue.dart | 💚 99 % |
pkgs/collection/lib/src/queue_list.dart | 💚 88 % ⬆️ 2 % |
pkgs/collection/lib/src/union_set.dart | 💚 100 % |
pkgs/collection/lib/src/union_set_controller.dart | 💚 100 % |
pkgs/collection/lib/src/unmodifiable_wrappers.dart | 💚 80 % ⬆️ 7 % |
pkgs/collection/lib/src/wrappers.dart | 💚 84 % ⬆️ 8 % |
This check for test coverage is informational (issues shown here will not fail the PR).
This check can be disabled by tagging the PR with skip-coverage-check
.
API leaks ✔️
The following packages contain symbols visible in the public API, but not exported by the library. Export these symbols or remove them from your publicly visible API.
Package | Leaked API symbols |
---|
License Headers ✔️
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
Files |
---|
no missing headers |
All source files should start with a license header.
class IterableZip<T> extends IterableBase<List<T>> { | ||
/// @nodoc | ||
@Deprecated('Use [i1, i2].zip from dart:collection') | ||
final class IterableZip<T> extends IterableBase<List<T>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we just do another "dot" release to deprecate and then drop this? Let's double down on the breaking changes.
What is the strategy for ecosystem rollout here, given that this package is pinned by flutter? |
Do not expect this to be able to land as-is. Jonas suggested not actually removing anything, just keeping it deprecated and And the changes to |
Just to elaborate on my comment, there are 3042 packages on pub which depend on package:collection https://pub.dev/packages?q=dependency%3Acollection. @sigurdm @jonasfj It isn't clear if this is transitive or immediate dependencies only which matters a lot here. Since flutter pins this package, every one of these packages will fail to resolve as soon as flutter ups its own dependency on package:collection. The potential scale of disruption here is huge, and this is exactly why we did not do breaking changes for these packages for null safety, we decided that releasing the breaking change as non-breaking would be less painful overall. |
Please mark this PR as a Draft if you don't intend to land it :). I would definitely argue we shouldn't do this, it is better to just keep the tech debt of the deprecated things forever. Or, we could delete them in a non-breaking change. I also think that would be better, even though it violates semver. |
(or an even better alternative, convince flutter to stop pinning deps, or at least have a process for not pinning certain specific deps for specified time periods) |
Any major version increment of a widely used package is hard. It probably requires a lot of packages to change their dependencies to That's what we simulate when we fiddle with version constraints of the SDK when publishing a new major version. Semver doesn't help. It can't, a major version increment is a breaking change, you have to opt in to it. The alternative is really just to release Or allow Pub to include two different versions of the same package in one program, so one package can use collection 1.19.0, and another 2.0.0. We could create |
I would find it incredibly worrisome if we choose to make a breaking change as a minor version increment because we can't figure out how to release a major version increment. Maybe we need a new concept: deprecation-aware semver. We'll have to have more kinds of deprecations if we want to make every later API change, like "this parameter will stop being optional" or "... will change type to", or "this class will become final/interface/base/sealed". Probably can't get away with "this enum will get more values". |
The package name The main reason to do a major bump of package:collection to version 2.0.0, would be if we don't want people to use both Maybe, conflicting extension methods could be a problem, but if you do a release of
I very much think we should do this! Especially, if packages with a dependency on |
The problem is that since flutter pins package:collection, the majority of users (and possibly packages, but I am not sure what percentage of these packaged depend on flutter) can't get a version solve for version 2 until flutter uses it. But we don't want flutter to use it until most packages support it, so its a catch 22. They can use a dependency override on package:collection, but do we want to tell >3k package authors to add a dependency override on a package for this purpose, and pre-emptively publish? We would need to communicate widely here what we expect package authors to do, and coordinate across the whole ecosystem to make this happen. And then the question becomes, what is the benefit we get in return for all that effort across not only our team but the entire package ecosystem, and is that a good investment.
I understand where you are coming from here, and usually I would agree. But, we need to take a step back and think about why we use semver, what the tradeoffs are, and ultimately what will best serve our users. Semver is just a tool - a tool that we use in order to avoid the pain of package version incompatibilities and breakage. However, it also comes with its own pain, which is that when you do a breaking change all consumers of your package have to take some action, even if they are not affected by the change. I would argue that when a package is core enough, and the breaking change is actually unlikely to anybody in practice, it is sometimes the best thing for our users to violate semver. I don't like it, but its important we try to evaluate which option is actually the least painful. And a breaking change in a core package like this is incredibly painful and costly. |
fwiw, I believe this is how Rust solves the diamond dependency problem. It allows the solve, but provisions two versions of the package locally. Things would get tricky if you ever start passing types from those packages around your app - TypeA from package.v1 would not be the same type as TypeA from package.v2. |
If you minimize the scope of breaking changes to things that are unlikely to be problems in practice and make all Dart Team packages use |
It is immediate dependencies See https://pub.dev/help/search#query-expressions. If you search for transitive dependencies (https://pub.dev/packages?q=dependency*%3Acollection) the result is a whooping 37400 packages. |
I think ideally we should harvest and move all often-used functionality into the standard libs, and deprecate this package entirely. It is not clear to me why we maintain this semi-standard library functionality in a package outside the dart sdk. I could also live with not marking the classes final and just keep things chugging along. In my opinion the value of these class markers is low compared to the breakage of either bumping the major version or breaking clients unexpectedly. (though Some more random comments:
I believe that allowing this would create more problems than it solves, and think that it would be a nightmare for the developer experience. If we made a feature along the lines of "private dependencies" as suggested by jonasfj, I think we could make it work from a technical standpoint (ie somehow prove that the types from two versions of the same package are never mixed)
That is (to some extent) what we do for the dart and flutter sdks https://github.com/dart-lang/sdk/blob/main/docs/process/breaking-changes.md and https://docs.flutter.dev/release/breaking-changes
I like that idea! That would actually provide a way of making progress without breaking users, while giving fair warnings. |
Move
package:collection
to using Dart 3.0 language features, in particularfinal
andinterface
classes.Remove deprecated APIs.
Deprecate a few new APIs for various reasons (like better versions being available in the platform libraries).
Includes a potentially breaking change to
SetEquality
andMapEquality
which should increase performance.Adding
final
orinterface
to classes requires a major version increment.That will be incredibly painful to land since
package:test
depends on collection^1.x.0
and everything in the world, this package included, depends onpackage:test
. (Half of that world depends onpackage:collection
directly as well, it's not justtest
.)For now (to be able to run tests), the pubspec version is
1.x.0-2.0.0.wip
.(The
CanonicalizedMap
class is only marked@sealed
, notfinal
, until an existing subclass can be fixed.It's unknown how many other packages will be broken by these class modifiers.)