-
Notifications
You must be signed in to change notification settings - Fork 33
Collection: Major version, use 3.0 language features. #859
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
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. |
If this is still alive, is it possible to include some missing methods to |
Isn't the point of To the point of major version release: We could just go ahead and land this. Then the slow process of moving the ecosystem to the new version will start, but the only downside I see is that there is a long window where we would have extra work if we wanted to patch the older version of package collection. That doesn't happen really, so we are good I think. I also think the ongoing work to unpinning Flutter package deps might help. |
Well, they have to be mixed in to something that ends up implementing |
Yes I guess we could have package:flutter have a wide constraint spanning I (as already said) don't see that we gain much from this. I think we should use And to repeat myself - why do we even have this package? Why not merge any useful stuff into dart:collection and discontinue this package? |
At least philosophically, I think we should try to move as much as possible out of the SDK. The more is in packages, the better.
For this PR specifically, I don't have any opinion if it's worth it. But generally we shouldn't hold back with bumping major versions. It won't break the ecosystem, just take longer to propagate. |
I would just add, that major version bump of package:collection will affect
a lot of packages.
It's a massive migration, lots of packages will be left behind.
The community might be better served by not breaking, forking, soft break,
two libraries in the package, or some variation of this.
Breaking feels a bit like something we'd do for aesthetics. As compared to
discontinuing the package in favor of a new package name.
Are there important interfaces that people implement here?
Is duplication of the code going to incur huge binary size cost?
I think the answer is largely no, maybe except Equality, but I don't think
it's something lots of users implement and share in their packages. If so
it could be re-exported from a new package name.
Just my two cents. Breaking version numbers are painful.
…--
Regards Jonas Finnemann Jensen.
tirs. 7. okt. 2025, 14.36 skrev Moritz ***@***.***>:
*mosuem* left a comment (dart-lang/core#859)
<#859 (comment)>
If this is still alive, is it possible to include some missing methods to
UnmodifiableMapMixin. It's missing addEntries, removeWhere, update,
updateAll methods.
Isn't the point of Unmodifiable to not have those methods? Or am I being
dense?
------------------------------
To the point of major version release: We could just go ahead and land
this. Then the slow process of moving the ecosystem to the new version will
start, but the only downside I see is that there is a long window where we
would have extra work if we wanted to patch the older version of package
collection. That doesn't happen really, so we are good I think.
I also think the ongoing work to unpinning Flutter package deps might help.
@sigurdm <https://github.com/sigurdm> @lrhn <https://github.com/lrhn>
Thoughts?
—
Reply to this email directly, view it on GitHub
<#859 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AABERZHLUNZODTXFCLXNQ2L3WOXUHAVCNFSM6AAAAABXALRBESVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTGNZWGY4TMNRRG4>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Then we should really introduce private dependencies, as these are all just workarounds to that... I agree with @lrhn that we should not end up in a state where we can't or won't do changes to packages as soon as they are widely used. I would rather have an ecosystem of evolving packages than a stale but stable one. |
Avoid breaking changes in popular packages is not the same as letting them
go stale.
That said, breaking changes today incur a lot of cost. We do have way to
migrate using constraints spanning two major versions, but the process is
slow and for popular packages it requires a lot actors to (a) take action
and update their package, and, (b) be careful with the constraints and what
functionality they depend on.
That's not impossible, but the cost is high. Thus, IMO the benefits from a
breaking change should be commensurate.
If we're just removing deprecated classes, maybe it's better to hide them
from dartdoc. If we're just adding final to class, maybe @Sealed is a
better compromise.
If we had better tooling for gradual migrations, such as
private_dependencies, the cost would be lower, and the benefit required for
a breaking change would not have to be as high.
I don't have fully considered thoughts on this, and it's certainly hard to
balance whether or not the benefit of breaking outweighs the cost.
ons. 8. okt. 2025, 09.29 skrev Moritz ***@***.***>:
… *mosuem* left a comment (dart-lang/core#859)
<#859 (comment)>
The community might be better served by not breaking, forking, soft break,
two libraries in the package, or some variation of this.
Then we should really introduce private dependencies, as these are all
just workarounds to that... I agree with @lrhn <https://github.com/lrhn>
that we should not end up in a state where we can't or won't do changes to
packages as soon as they are widely used. I would rather have an ecosystem
of evolving packages than a stale but stable one.
But this are my more general thoughts, if these changes are not worth that
happy to not land this.
—
Reply to this email directly, view it on GitHub
<#859 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AABERZDPWNKTQLKEERUCNY33WS4LXAVCNFSM6AAAAABXALRBESVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTGOBQGEYTMNBUGY>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
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.)