Always narrow markers by Python version#6076
Conversation
| let not_markers = simplify_python(markers.negate(), python_requirement); | ||
| let mut new_markers = markers.clone(); | ||
| new_markers.and(fork.markers.negate()); | ||
| new_markers.and(simplify_python(fork.markers.negate(), python_requirement)); |
There was a problem hiding this comment.
Honestly, I can't figure out if the simplify_python here is necessary. It's definitely necessary on not_markers above... But I'm not sure about here. I don't think any tests change when I remove it.
There was a problem hiding this comment.
Logically, I think this simplification here is right and even necessary. Because while fork.markers, by construction, should already be simplified, negating that here might wind up with a marker that includes (or is completely excluded by) requires-python. So re-doing the simplification does indeed seem correct.
I think that the same simplification ought to happen to markers too right? Specifically, before passing it to fork.intersect below.
There was a problem hiding this comment.
I thought markers was already simplified because we simplify when generating PubGrubDependency.
There was a problem hiding this comment.
There was a problem hiding this comment.
Talking over DM, the markers here are already simplified.
5f09f9b to
817ff9f
Compare
| // Requires-Python specifier. | ||
| requires_python = requires_python.unwrap() | ||
| let requirement = if let Some(requires_python) = python_requirement.target().and_then(|target| target.as_requires_python()).filter(|_| !requirement.marker.is_true()) { | ||
| let marker = requirement.marker.clone().simplify_python_versions( |
There was a problem hiding this comment.
Hmm.. I'm surprised this works. It's important to note that calling simplify_python_versions creates a different marker tree than the same tree parsed from the lockfile. For example, if we restrict the range of python_version >= 3.12 given requires-python >= 3.9, we now get the marker tree [3.9,3.12)=>false,[3.12,inf)=>true. If you parse that same expression from the lockfile, you get [0,3.12)=>false,[3.12,inf)=>true, which is not equal. This can cause false negatives in a --locked operation. Current we solve this by reapplying the requires-python restriction after parsing the lockfile. However, I believe this PR now restricts markers to the narrowed python requirement, which we do not reapply, so I'm surprised we aren't seeing failing test cases. Is there something I'm missing and can we remove the extra code in Lock::from_toml now?
There was a problem hiding this comment.
That is... interesting. If I remove that code from from_toml, lock_conditional_dependency_extra fails (for one).
There was a problem hiding this comment.
It may just be that this doesn't work (for that particular problem) and we just don't have a proper test case for it.
There was a problem hiding this comment.
Any other ideas on how we can solve this problem?
There was a problem hiding this comment.
I think we could fix this by removing the whole idea of a restricted range with a special "rest" range, and then only apply simplification when displaying a marker, using disjointness checks instead for requires-python filtering. I was hoping we could use restriction to our advantage but it seems that may not be possible with narrowing.
There was a problem hiding this comment.
I guess the other option is: try to write this to the lockfile.
There was a problem hiding this comment.
Can we use environment-markers to try and enforce this narrowing when we deserialize the lockfile?
There was a problem hiding this comment.
Maybe.. how would we map those to requirements?
There was a problem hiding this comment.
Could we instead just retain the lower-bound, like >=3.7, <3.10 instead of using <3.10? Does that cause other problems?
There was a problem hiding this comment.
Ok @ibraheemdev -- I added a failing test for this.
6542938 to
a838135
Compare
a838135 to
07acef6
Compare
|
I think something like this is critical (it fixes a bunch of issues), but need some solution to the problem Ibraheem brought up. Let me try to write a test for it. |
crates/uv/tests/lock.rs
Outdated
| warning: `uv lock` is experimental and may change without warning | ||
| warning: `uv.sources` is experimental and may change without warning | ||
| Resolved 3 packages in [TIME] | ||
| error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. |
There was a problem hiding this comment.
This instability is due to the problem @ibraheemdev mentioned.
8cf3649 to
3c213ed
Compare
|
@ibraheemdev -- Maybe an alternative idea... Could we add |
|
Hmm, but that might not cover |
|
I think we can safely do this if we merge #6091? |
3c213ed to
5c2ddaf
Compare
d22125a to
8bf6ed1
Compare
) ## Summary This PR changes the definition of `--locked` from: > Produces the same `Lock` To: > Passes `Lock::satisfies` This is a subtle but important difference. Previous, if `Lock::satisfies` failed, we would run a resolution, then do `existing_lock == lock`. If the two weren't equal, and `--locked` was specified, we'd throw an error. The equality check is hard to get right. For example, it means that we can't ship #6076 without changing our marker representation, since the deserialized lockfile "loses" some of the internal marker state that gets accumulated during resolution. The downside of this change is that there could be scenarios in which `uv lock --locked` fails even though the lockfile would actually work and the exact TOML would be unchanged. But... I think it's ok if `--locked` fails after the user modifies something?
| "python_version >= '3.12' and python_version < '3.13'", | ||
| "python_version < '3.12'", | ||
| "python_version < '3.13'", | ||
| "python_version >= '3.13'", |
There was a problem hiding this comment.
shouldn't this remove too? since it doesn't fill in >=3.11, <3.12 required range.
There was a problem hiding this comment.
We don't respect upper-bounds on requires-python.
BurntSushi
left a comment
There was a problem hiding this comment.
I think I found one possible additional simplification that should be done, but otherwise I think this LGTM assuming @ibraheemdev's concern is addressed. (I think it is now with #6102 merged?)
ibraheemdev
left a comment
There was a problem hiding this comment.
This looks good to me now that we no longer rely on marker equality for --locked.
8bf6ed1 to
35aac88
Compare
## Summary This is no longer required since we no longer implement `Eq` on `Lock`. It will also sometimes be "wrong" as of #6076, since we now apply different `requires-python` filtering to different parts of the tree during resolution.
Summary
Using #6064 as a motivating example: at present, on main, we're not properly propagating the
Requires-Pythonsimplifications. In that case, for example, we end up solving for a branch withpython_version < 3.11, and a branch>= 3.11, even thoughRequires-Pythonis>=3.11. Later, when we get to the graph, we apply version simplification based onRequires-Python, which causes us to remove thepython_version < 3.11markers entirely, leaving us with duplicate dependencies forpylint.This PR instead tries to ensure that we always apply this narrowing to requirements and forks, so that we don't need to apply the same simplification when constructing the graph at all.
Closes #6064.
Closes #6059.