-
Notifications
You must be signed in to change notification settings - Fork 39
Experimental new Interval API with continuous space for versions #99
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
Conversation
It is a big change, I could use some pointers on where to start looking this over. What are the core new ideas? What advantages do they bring? |
The core idea of this API design attempt is to relax the So the traits to implement by the user are the following. pub trait Version: Clone + Ord + Debug + Display {
/// Returns the minimum version.
fn minimum() -> Bound<Self>;
/// Returns the maximum version.
fn maximum() -> Bound<Self>;
}
/// An interval is a bounded domain containing all values
/// between its starting and ending bounds.
pub trait Interval<V>: RangeBounds<V> + Debug + Clone + Eq + PartialEq {
/// Create an interval from its starting and ending bounds.
/// It's the caller responsability to order them correctly.
fn new(start_bound: Bound<V>, end_bound: Bound<V>) -> Self;
} The pub trait RangeBounds<T> {
pub fn start_bound(&self) -> Bound<&T>;
pub fn end_bound(&self) -> Bound<&T>;
pub fn contains<U>(&self, item: &U) -> bool { ... } This means giving the start and the end of the interval as bounds (potentially infinite). And a But the cool change is that the pub struct Range<I, V> {
segments: SmallVec<I>,
phantom: PhantomData<V>,
} So basically the same as before with the exception that it now has two type variables, one for impl<I: Interval<V>, V: Version> Range<I, V> {
...
} Since The downside is that the user cannot override the behavior for versions outside of a given interval, but I don't see that being a desirable property. The user also do not see the full But the huge advantage is that we keep the control of |
To have a look at what's new, you can just look at the |
I think this will be cleaner with a associated type for |
Thanks for the explanation! I had a few minutes to look this over. It is definitely an intriguing approach! I am not seeing how to construct a |
Well if your semver type is something like impl SemVer {
fn new(major: u32, minor: u32, patch: u32, pre: Option<String>) -> Self { ... }
} Then you'd just call SemInterval::new(Bound::Included(SemVer::new(2, 0, 0, Some("alpha"))), Bound::Unbounded) And within the impl RangeBounds<SemVer> for SemInterval {
fn start_bound(&self) -> Bound<&SemVer> { ... }
fn end_bound(&self) -> Bound<&SemVer> { ... }
// this one we override the default implementation provided by `RangeBounds`
fn contains(&self, item: &SemVer) -> bool {
// 2.0.0-beta
if item.is_prerelease() {
// 2.0.0-alpha 2.0.0-alpha, 2.0.0
self.start.is_prerelease() && item.within_bounds(self.start, self.start.without_prerelease())
// 4.5.3-beta, 4.5.3-alpha, 4.5.3-beta, 4.5.3 4.5.3-beta
|| self.end.is_prerelease() && item.lower_than(self.end) && item.without_prerelease().higher_than(self.end)
} else {
item.within_bounds(self.start, self.end)
}
}
} It's more or less pseudo code so take that with a grain of salt as I've not thought through it all, just writing stuff as it comes to mind right now. |
PS, I've edited the above example code for |
The above code example might be horribly wrong, the important notion is that "if we can explain in english with simple rules if a version (pre-release, or not) is included in an interval by just looking at the interval bounds, then we can do it in code too" |
Oh maybe I misinterpret the question. So the thing is that See for example here: Line 293 in 948d2c4
I should definitely add some ascii segments drawing for each branch of that |
That all makes sense except for one small part. When I call |
Well we expose the same API as before for /// Set of all versions comprised between two given versions.
/// The lower bound is included and the higher bound excluded.
/// `v1 <= v < v2`.
pub fn between(v1: impl Into<V>, v2: impl Into<V>) -> Self {
let start_bound = Bound::Included(v1.into());
let end_bound = Bound::Excluded(v2.into());
Self {
segments: SmallVec::one(I::new(start_bound, end_bound)),
phantom: PhantomData,
}
} That's why I needed to add the That's what you'll find here: Line 111 in 948d2c4
|
It took a few readings before this sank in. But now that I get it, it sounds worth a try! |
If the self.into_version_req().matches(version) Of course we could just rewrite the content of that function, that would be more efficient and simpler than doing the |
So one thing we are going to need to be careful of is that
|
Hum, very true ... Breaking the strict meaning of |
Do we know how dart pub handles such a situation? |
So I cloned pub and added the following test: test('backtracking to pre-release after NoVersions', () async {
await servePackages((builder) {
builder.serve('a', '1.1.0', deps: {'b': '^1.0.0'});
builder.serve('b', '1.1.0-alpha');
builder.serve('a', '1.0.0', deps: {'b': '^1.1.0-alpha'});
});
await d.appDir({
'a': '^1.0.0',
}).create();
await expectResolves(result: {
'a': '1.1.0',
'b': '1.1.0-alpha',
});
}); And that test is passing. Meaning they accept |
Hum, however, if I go one dependency deeper to force a backtracking to happen, it seems to make pub solver fail! test('backtracking to pre-release after NoVersions', () async {
await servePackages((builder) {
builder.serve('a', '1.1.0', deps: {'b': '^1.0.0'});
builder.serve('b', '1.0.0', deps: {'c': '^1.0.0'});
builder.serve('c', '0.9.0');
builder.serve('b', '1.1.0-alpha');
builder.serve('a', '1.0.0', deps: {'b': '^1.1.0-alpha'});
});
await d.appDir({
'a': '^1.0.0',
}).create();
await expectResolves();
// await expectResolves(result: {
// 'a': '1.1.0',
// 'b': '1.1.0-alpha',
// });
}); This test fails even though I'm just asking it |
I've created a PR on pub repo with the failing test asking for their thoughts: dart-lang/pub#3038 |
I was going to do more research but I thought the answer was "by having more Range like rules around when pre releases match", looks like you did the research and found the real answer is "with bugs". |
Don't bother looking at those new commits. I'm trying stuff to figure out what is wrong with the intervals. For now, I've added logging, which lead me to discover that one assignment intersection was wrong at some point. Leading me to the fact that there is a problem with
Then we have Typically, this should have been spotted by the |
I've temporarily deactivated the serde feature and all tests (except with serde) are passing :) I didn't found a way to trigger the symmetric intersection tests though, so I just fixed the bug in Before evaluating how much of a performance drain is the usage of bounds, I thus need to fix all the code related to the serde feature, to be able to run the benchmarks. I'll probably also have to re-encode the ron files. I'm not sure how I'll do that but will see. |
The code in this branch will also need a decent amount of cleaning. And while evaluating perf, verifying that the |
If there is a bug in intersection, let's make the fix a separate PR so we can get it out there ASAP. |
Nope no worry, there was a bug in my reimplementation of intersection for
the Interval API in this PR. The main one that is released is fine.
…On Sun, Aug 8, 2021, 16:05 Jacob Finkelman ***@***.***> wrote:
If there is a bug in intersection, let's make the fix a separate PR so we
can get it out there ASAP.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#99 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAWFOCLGP3OO522HKD5FHSDT32FJJANCNFSM47EEHCYA>
.
|
This has been superseded by #112 |
Tests are not passing yet. It is quite a big change so some bugs were inserted (not on purpose ^^)