Skip to content

Commit 7a177a9

Browse files
committed
Encode mutually-incompatible pairs of markers
1 parent d187535 commit 7a177a9

File tree

5 files changed

+156
-35
lines changed

5 files changed

+156
-35
lines changed

crates/uv-distribution/src/metadata/lowering.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ impl LoweredRequirement {
314314
})
315315
.chain(std::iter::once(Ok(remaining)))
316316
.filter(|requirement| match requirement {
317-
Ok(requirement) => !requirement.0.marker.is_false(),
317+
Ok(requirement) => !(requirement.0.marker.is_false() || requirement.0.marker.is_conflicting()),
318318
Err(_) => true,
319319
}),
320320
)
@@ -466,7 +466,7 @@ impl LoweredRequirement {
466466
})
467467
.chain(std::iter::once(Ok(remaining)))
468468
.filter(|requirement| match requirement {
469-
Ok(requirement) => !requirement.0.marker.is_false(),
469+
Ok(requirement) => !(requirement.0.marker.is_false() || requirement.0.marker.is_conflicting()),
470470
Err(_) => true,
471471
}),
472472
)

crates/uv-pep508/src/marker/algebra.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ use crate::marker::lowering::{
6060
LoweredMarkerValueExtra, LoweredMarkerValueString, LoweredMarkerValueVersion,
6161
};
6262
use crate::marker::MarkerValueExtra;
63-
use crate::ExtraOperator;
63+
use crate::{ExtraOperator, MarkerTree};
6464
use crate::{MarkerExpression, MarkerOperator, MarkerValueVersion};
6565

6666
/// The global node interner.
@@ -396,6 +396,7 @@ impl InternerGuard<'_> {
396396
}
397397
}
398398

399+
399400
// Restrict the output of a given boolean variable in the tree.
400401
//
401402
// If the provided function `f` returns a `Some` boolean value, the tree will be simplified

crates/uv-pep508/src/marker/tree.rs

Lines changed: 147 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::cmp::Ordering;
22
use std::fmt::{self, Display, Formatter};
33
use std::ops::{Bound, Deref};
44
use std::str::FromStr;
5-
5+
use std::sync::LazyLock;
66
use itertools::Itertools;
77
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
88
use uv_normalize::ExtraName;
@@ -16,9 +16,7 @@ use crate::marker::lowering::{
1616
LoweredMarkerValueExtra, LoweredMarkerValueString, LoweredMarkerValueVersion,
1717
};
1818
use crate::marker::parse;
19-
use crate::{
20-
MarkerEnvironment, Pep508Error, Pep508ErrorSource, Pep508Url, Reporter, TracingReporter,
21-
};
19+
use crate::{MarkerEnvironment, Pep508Error, Pep508ErrorSource, Pep508Url, Reporter, TracingReporter, VerbatimUrl};
2220

2321
/// Ways in which marker evaluation can fail
2422
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
@@ -685,6 +683,150 @@ impl MarkerTree {
685683
self.0.is_false()
686684
}
687685

686+
/// Whether the marker is known to be unsatisfiable.
687+
///
688+
/// For example, while the marker specification and grammar do not _forbid_ it, we know that
689+
/// both `sys_platform == 'win32'` and `platform_system == 'Darwin'` will never true at the
690+
/// same time.
691+
///
692+
/// This method thus encodes assumptions about the environment that are not guaranteed by the
693+
/// PEP 508 specification alone.
694+
pub fn is_conflicting(&self) -> bool {
695+
static MUTUAL_EXCLUSIONS: LazyLock<MarkerTree> = LazyLock::new(|| {
696+
let mut tree = MarkerTree::FALSE;
697+
for (a, b) in [
698+
// sys_platform == 'darwin' and platform_system == 'Windows'
699+
(MarkerExpression::String {
700+
key: MarkerValueString::SysPlatform,
701+
operator: MarkerOperator::Equal,
702+
value: "darwin".to_string(),
703+
}, MarkerExpression::String {
704+
key: MarkerValueString::PlatformSystem,
705+
operator: MarkerOperator::Equal,
706+
value: "Windows".to_string(),
707+
}),
708+
// sys_platform == 'darwin' and platform_system == 'Linux'
709+
(MarkerExpression::String {
710+
key: MarkerValueString::SysPlatform,
711+
operator: MarkerOperator::Equal,
712+
value: "darwin".to_string(),
713+
}, MarkerExpression::String {
714+
key: MarkerValueString::PlatformSystem,
715+
operator: MarkerOperator::Equal,
716+
value: "Linux".to_string(),
717+
}),
718+
// sys_platform == 'win32' and platform_system == 'Darwin'
719+
(MarkerExpression::String {
720+
key: MarkerValueString::SysPlatform,
721+
operator: MarkerOperator::Equal,
722+
value: "win32".to_string(),
723+
}, MarkerExpression::String {
724+
key: MarkerValueString::PlatformSystem,
725+
operator: MarkerOperator::Equal,
726+
value: "Darwin".to_string(),
727+
}),
728+
// sys_platform == 'win32' and platform_system == 'Linux'
729+
(MarkerExpression::String {
730+
key: MarkerValueString::SysPlatform,
731+
operator: MarkerOperator::Equal,
732+
value: "win32".to_string(),
733+
}, MarkerExpression::String {
734+
key: MarkerValueString::PlatformSystem,
735+
operator: MarkerOperator::Equal,
736+
value: "Linux".to_string(),
737+
}),
738+
// sys_platform == 'linux' and platform_system == 'Darwin'
739+
(MarkerExpression::String {
740+
key: MarkerValueString::SysPlatform,
741+
operator: MarkerOperator::Equal,
742+
value: "linux".to_string(),
743+
}, MarkerExpression::String {
744+
key: MarkerValueString::PlatformSystem,
745+
operator: MarkerOperator::Equal,
746+
value: "Darwin".to_string(),
747+
}),
748+
// sys_platform == 'linux' and platform_system == 'Windows'
749+
(MarkerExpression::String {
750+
key: MarkerValueString::SysPlatform,
751+
operator: MarkerOperator::Equal,
752+
value: "linux".to_string(),
753+
}, MarkerExpression::String {
754+
key: MarkerValueString::PlatformSystem,
755+
operator: MarkerOperator::Equal,
756+
value: "Windows".to_string(),
757+
}),
758+
// os_name == 'nt' and sys_platform == 'darwin'
759+
(MarkerExpression::String {
760+
key: MarkerValueString::OsName,
761+
operator: MarkerOperator::Equal,
762+
value: "nt".to_string(),
763+
}, MarkerExpression::String {
764+
key: MarkerValueString::SysPlatform,
765+
operator: MarkerOperator::Equal,
766+
value: "darwin".to_string(),
767+
}),
768+
// os_name == 'nt' and sys_platform == 'linux'
769+
(MarkerExpression::String {
770+
key: MarkerValueString::OsName,
771+
operator: MarkerOperator::Equal,
772+
value: "nt".to_string(),
773+
}, MarkerExpression::String {
774+
key: MarkerValueString::SysPlatform,
775+
operator: MarkerOperator::Equal,
776+
value: "linux".to_string(),
777+
}),
778+
// os_name == 'posix' and sys_platform == 'win32'
779+
(MarkerExpression::String {
780+
key: MarkerValueString::OsName,
781+
operator: MarkerOperator::Equal,
782+
value: "posix".to_string(),
783+
}, MarkerExpression::String {
784+
key: MarkerValueString::SysPlatform,
785+
operator: MarkerOperator::Equal,
786+
value: "win32".to_string(),
787+
}),
788+
// os_name == 'nt' and platform_system == 'Darwin'
789+
(MarkerExpression::String {
790+
key: MarkerValueString::OsName,
791+
operator: MarkerOperator::Equal,
792+
value: "nt".to_string(),
793+
}, MarkerExpression::String {
794+
key: MarkerValueString::PlatformSystem,
795+
operator: MarkerOperator::Equal,
796+
value: "Darwin".to_string(),
797+
}),
798+
// os_name == 'nt' and platform_system == 'Linux'
799+
(MarkerExpression::String {
800+
key: MarkerValueString::OsName,
801+
operator: MarkerOperator::Equal,
802+
value: "nt".to_string(),
803+
}, MarkerExpression::String {
804+
key: MarkerValueString::PlatformSystem,
805+
operator: MarkerOperator::Equal,
806+
value: "Linux".to_string(),
807+
}),
808+
// os_name == 'posix' and platform_system == 'Windows'
809+
(MarkerExpression::String {
810+
key: MarkerValueString::OsName,
811+
operator: MarkerOperator::Equal,
812+
value: "posix".to_string(),
813+
}, MarkerExpression::String {
814+
key: MarkerValueString::PlatformSystem,
815+
operator: MarkerOperator::Equal,
816+
value: "Windows".to_string(),
817+
}),
818+
] {
819+
let mut a = MarkerTree::expression(a);
820+
let b = MarkerTree::expression(b);
821+
a.and(b);
822+
tree.or(a);
823+
}
824+
tree.negate()
825+
});
826+
827+
self.is_disjoint(&MUTUAL_EXCLUSIONS)
828+
}
829+
688830
/// Returns a new marker tree that is the negation of this one.
689831
#[must_use]
690832
pub fn negate(&self) -> MarkerTree {
@@ -727,6 +869,7 @@ impl MarkerTree {
727869
INTERNER.lock().is_disjoint(self.0, other.0)
728870
}
729871

872+
730873
/// Returns the contents of this marker tree, if it contains at least one expression.
731874
///
732875
/// If the marker is `true`, this method will return `None`.

crates/uv-resolver/src/resolver/environment.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ impl<'d> Forker<'d> {
494494
let mut envs = vec![];
495495
{
496496
let not_marker = self.marker.negate();
497-
if !env_marker.is_disjoint(&not_marker) {
497+
if !env_marker.is_disjoint(&not_marker) && !env_marker.is_conflicting() {
498498
envs.push(env.narrow_environment(not_marker));
499499
}
500500
}

crates/uv/tests/it/lock.rs

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18829,7 +18829,7 @@ fn lock_multiple_sources_respect_marker() -> Result<()> {
1882918829
----- stdout -----
1883018830

1883118831
----- stderr -----
18832-
Resolved 3 packages in [TIME]
18832+
Resolved 2 packages in [TIME]
1883318833
"###);
1883418834

1883518835
let lock = context.read("uv.lock");
@@ -18841,11 +18841,6 @@ fn lock_multiple_sources_respect_marker() -> Result<()> {
1884118841
lock, @r###"
1884218842
version = 1
1884318843
requires-python = ">=3.12"
18844-
resolution-markers = [
18845-
"platform_system == 'Windows' and sys_platform == 'darwin'",
18846-
"platform_system == 'Windows' and sys_platform != 'darwin'",
18847-
"platform_system != 'Windows'",
18848-
]
1884918844

1885018845
[options]
1885118846
exclude-newer = "2024-03-25T00:00:00Z"
@@ -18854,39 +18849,21 @@ fn lock_multiple_sources_respect_marker() -> Result<()> {
1885418849
name = "iniconfig"
1885518850
version = "2.0.0"
1885618851
source = { registry = "https://pypi.org/simple" }
18857-
resolution-markers = [
18858-
"platform_system == 'Windows' and sys_platform != 'darwin'",
18859-
]
1886018852
sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
1886118853
wheels = [
1886218854
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
1886318855
]
1886418856

18865-
[[package]]
18866-
name = "iniconfig"
18867-
version = "2.0.0"
18868-
source = { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl" }
18869-
resolution-markers = [
18870-
"platform_system == 'Windows' and sys_platform == 'darwin'",
18871-
]
18872-
wheels = [
18873-
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" },
18874-
]
18875-
1887618857
[[package]]
1887718858
name = "project"
1887818859
version = "0.1.0"
1887918860
source = { virtual = "." }
1888018861
dependencies = [
18881-
{ name = "iniconfig", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "platform_system == 'Windows' and sys_platform != 'darwin'" },
18882-
{ name = "iniconfig", version = "2.0.0", source = { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl" }, marker = "platform_system == 'Windows' and sys_platform == 'darwin'" },
18862+
{ name = "iniconfig", marker = "platform_system == 'Windows' and sys_platform != 'darwin'" },
1888318863
]
1888418864

1888518865
[package.metadata]
18886-
requires-dist = [
18887-
{ name = "iniconfig", marker = "platform_system == 'Windows' and sys_platform != 'darwin'" },
18888-
{ name = "iniconfig", marker = "platform_system == 'Windows' and sys_platform == 'darwin'", url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl" },
18889-
]
18866+
requires-dist = [{ name = "iniconfig", marker = "platform_system == 'Windows' and sys_platform != 'darwin'" }]
1889018867
"###
1889118868
);
1889218869
});
@@ -18898,7 +18875,7 @@ fn lock_multiple_sources_respect_marker() -> Result<()> {
1889818875
----- stdout -----
1889918876

1890018877
----- stderr -----
18901-
Resolved 3 packages in [TIME]
18878+
Resolved 2 packages in [TIME]
1890218879
"###);
1890318880

1890418881
Ok(())

0 commit comments

Comments
 (0)