Skip to content

Commit

Permalink
Move into algebra
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Nov 29, 2024
1 parent 801b71c commit 7580a50
Show file tree
Hide file tree
Showing 13 changed files with 482 additions and 354 deletions.
345 changes: 333 additions & 12 deletions crates/uv-pep508/src/marker/algebra.rs

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions crates/uv-pep508/src/marker/lowering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,23 @@ impl From<CanonicalMarkerValueVersion> for MarkerValueVersion {
/// Those environment markers with an arbitrary string as value such as `sys_platform`
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum CanonicalMarkerValueString {
/// `implementation_name`
ImplementationName,
/// `os_name`
OsName,
/// `sys_platform`
SysPlatform,
/// `platform_system`
PlatformSystem,
/// `platform_machine`
PlatformMachine,
/// Deprecated `platform.machine` from <https://peps.python.org/pep-0345/#environment-markers>
/// `platform_python_implementation`
PlatformPythonImplementation,
/// `platform_release`
PlatformRelease,
/// `platform_system`
PlatformSystem,
/// `platform_version`
PlatformVersion,
/// `sys_platform`
SysPlatform,
/// `implementation_name`
ImplementationName,
}

impl From<MarkerValueString> for CanonicalMarkerValueString {
Expand Down
215 changes: 11 additions & 204 deletions crates/uv-pep508/src/marker/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use std::cmp::Ordering;
use std::fmt::{self, Display, Formatter};
use std::ops::{Bound, Deref};
use std::str::FromStr;
use std::sync::LazyLock;

use itertools::Itertools;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
Expand Down Expand Up @@ -697,14 +696,12 @@ impl MarkerTree {
#[allow(clippy::needless_pass_by_value)]
pub fn and(&mut self, tree: MarkerTree) {
self.0 = INTERNER.lock().and(self.0, tree.0);
self.simplify();
}

/// Combine this marker tree with the one given via a disjunction.
#[allow(clippy::needless_pass_by_value)]
pub fn or(&mut self, tree: MarkerTree) {
self.0 = INTERNER.lock().or(self.0, tree.0);
self.simplify();
}

/// Sets this to a marker equivalent to the implication of this one and the
Expand All @@ -720,23 +717,6 @@ impl MarkerTree {
self.or(consequent);
}

/// Simplify the marker, namely, by detecting known impossible conditions.
///
/// For example, while the marker specification and grammar do not _forbid_ it, we know that
/// both `sys_platform == 'win32'` and `platform_system == 'Darwin'` will never true at the
/// same time.
///
/// This method thus encodes assumptions about the environment that are not guaranteed by the
/// PEP 508 specification alone.
fn simplify(&mut self) {
if !self.0.is_false() {
let mutex = &*MUTUAL_EXCLUSIONS;
if INTERNER.lock().is_disjoint(self.0, mutex.0) {
*self = MarkerTree::FALSE;
}
}
}

/// Returns `true` if there is no environment in which both marker trees can apply,
/// i.e. their conjunction is always `false`.
///
Expand All @@ -745,9 +725,7 @@ impl MarkerTree {
/// false negatives, i.e. it may not be able to detect that two markers are disjoint for
/// complex expressions.
pub fn is_disjoint(&self, other: &MarkerTree) -> bool {
let mutex = &*MUTUAL_EXCLUSIONS;
let node = INTERNER.lock().and(self.0, other.0);
node.is_false() || INTERNER.lock().is_disjoint(node, mutex.0)
INTERNER.lock().is_disjoint(self.0, other.0)
}

/// Returns the contents of this marker tree, if it contains at least one expression.
Expand Down Expand Up @@ -1595,175 +1573,6 @@ impl schemars::JsonSchema for MarkerTree {
}
}

static MUTUAL_EXCLUSIONS: LazyLock<MarkerTree> = LazyLock::new(|| {
let mut tree = NodeId::FALSE;
for (a, b) in [
// sys_platform == 'darwin' and platform_system == 'Windows'
(
MarkerExpression::String {
key: MarkerValueString::SysPlatform,
operator: MarkerOperator::Equal,
value: "darwin".to_string(),
},
MarkerExpression::String {
key: MarkerValueString::PlatformSystem,
operator: MarkerOperator::Equal,
value: "Windows".to_string(),
},
),
// sys_platform == 'darwin' and platform_system == 'Linux'
(
MarkerExpression::String {
key: MarkerValueString::SysPlatform,
operator: MarkerOperator::Equal,
value: "darwin".to_string(),
},
MarkerExpression::String {
key: MarkerValueString::PlatformSystem,
operator: MarkerOperator::Equal,
value: "Linux".to_string(),
},
),
// sys_platform == 'win32' and platform_system == 'Darwin'
(
MarkerExpression::String {
key: MarkerValueString::SysPlatform,
operator: MarkerOperator::Equal,
value: "win32".to_string(),
},
MarkerExpression::String {
key: MarkerValueString::PlatformSystem,
operator: MarkerOperator::Equal,
value: "Darwin".to_string(),
},
),
// sys_platform == 'win32' and platform_system == 'Linux'
(
MarkerExpression::String {
key: MarkerValueString::SysPlatform,
operator: MarkerOperator::Equal,
value: "win32".to_string(),
},
MarkerExpression::String {
key: MarkerValueString::PlatformSystem,
operator: MarkerOperator::Equal,
value: "Linux".to_string(),
},
),
// sys_platform == 'linux' and platform_system == 'Darwin'
(
MarkerExpression::String {
key: MarkerValueString::SysPlatform,
operator: MarkerOperator::Equal,
value: "linux".to_string(),
},
MarkerExpression::String {
key: MarkerValueString::PlatformSystem,
operator: MarkerOperator::Equal,
value: "Darwin".to_string(),
},
),
// sys_platform == 'linux' and platform_system == 'Windows'
(
MarkerExpression::String {
key: MarkerValueString::SysPlatform,
operator: MarkerOperator::Equal,
value: "linux".to_string(),
},
MarkerExpression::String {
key: MarkerValueString::PlatformSystem,
operator: MarkerOperator::Equal,
value: "Windows".to_string(),
},
),
// os_name == 'nt' and sys_platform == 'darwin'
(
MarkerExpression::String {
key: MarkerValueString::OsName,
operator: MarkerOperator::Equal,
value: "nt".to_string(),
},
MarkerExpression::String {
key: MarkerValueString::SysPlatform,
operator: MarkerOperator::Equal,
value: "darwin".to_string(),
},
),
// os_name == 'nt' and sys_platform == 'linux'
(
MarkerExpression::String {
key: MarkerValueString::OsName,
operator: MarkerOperator::Equal,
value: "nt".to_string(),
},
MarkerExpression::String {
key: MarkerValueString::SysPlatform,
operator: MarkerOperator::Equal,
value: "linux".to_string(),
},
),
// os_name == 'posix' and sys_platform == 'win32'
(
MarkerExpression::String {
key: MarkerValueString::OsName,
operator: MarkerOperator::Equal,
value: "posix".to_string(),
},
MarkerExpression::String {
key: MarkerValueString::SysPlatform,
operator: MarkerOperator::Equal,
value: "win32".to_string(),
},
),
// os_name == 'nt' and platform_system == 'Darwin'
(
MarkerExpression::String {
key: MarkerValueString::OsName,
operator: MarkerOperator::Equal,
value: "nt".to_string(),
},
MarkerExpression::String {
key: MarkerValueString::PlatformSystem,
operator: MarkerOperator::Equal,
value: "Darwin".to_string(),
},
),
// os_name == 'nt' and platform_system == 'Linux'
(
MarkerExpression::String {
key: MarkerValueString::OsName,
operator: MarkerOperator::Equal,
value: "nt".to_string(),
},
MarkerExpression::String {
key: MarkerValueString::PlatformSystem,
operator: MarkerOperator::Equal,
value: "Linux".to_string(),
},
),
// os_name == 'posix' and platform_system == 'Windows'
(
MarkerExpression::String {
key: MarkerValueString::OsName,
operator: MarkerOperator::Equal,
value: "posix".to_string(),
},
MarkerExpression::String {
key: MarkerValueString::PlatformSystem,
operator: MarkerOperator::Equal,
value: "Windows".to_string(),
},
),
] {
let mut interner = INTERNER.lock();
let a = interner.expression(a);
let b = interner.expression(b);
let a_and_b = interner.and(a, b);
tree = interner.or(tree, a_and_b);
}
MarkerTree(tree).negate()
});

#[cfg(test)]
mod test {
use std::ops::Bound;
Expand Down Expand Up @@ -2661,13 +2470,13 @@ mod test {
or (implementation_name != 'pypy' and sys_platform == 'win32')
or (sys_platform == 'win32' and os_name != 'nt')
or (sys_platform != 'win32' and os_name == 'nt')",
"(os_name != 'nt' and sys_platform == 'win32') \
or (implementation_name != 'pypy' and os_name == 'nt') \
or (implementation_name == 'pypy' and os_name != 'nt') \
or (os_name == 'nt' and sys_platform != 'win32')",
"(sys_platform != 'win32' and implementation_name == 'pypy') \
or (os_name != 'nt' and sys_platform == 'win32') \
or (os_name == 'nt' and sys_platform != 'win32') \
or (sys_platform == 'win32' and implementation_name != 'pypy')",
);

// This is another case we cannot simplify fully, depending on the variable order.
// This is a case we can simplify fully, but it's dependent on the variable order.
// The expression is equivalent to `sys_platform == 'x' or (os_name == 'Linux' and platform_system == 'win32')`.
assert_simplifies(
"(os_name == 'Linux' and platform_system == 'win32')
Expand All @@ -2676,14 +2485,14 @@ mod test {
or (os_name != 'Linux' and platform_system == 'win32' and sys_platform == 'x')
or (os_name == 'Linux' and platform_system != 'win32' and sys_platform == 'x')
or (os_name != 'Linux' and platform_system != 'win32' and sys_platform == 'x')",
"(os_name != 'Linux' and sys_platform == 'x') or (platform_system != 'win32' and sys_platform == 'x') or (os_name == 'Linux' and platform_system == 'win32')",
"(os_name == 'Linux' and platform_system == 'win32') or sys_platform == 'x'",
);

assert_simplifies("python_version > '3.7'", "python_full_version >= '3.8'");

assert_simplifies(
"(python_version <= '3.7' and os_name == 'Linux') or python_version > '3.7'",
"os_name == 'Linux' or python_full_version >= '3.8'",
"python_full_version >= '3.8' or os_name == 'Linux'",
);

// Again, the extra `<3.7` and `>=3.9` expressions cannot be seen as redundant due to them being interdependent.
Expand All @@ -2692,9 +2501,7 @@ mod test {
"(os_name == 'Linux' and sys_platform == 'win32') \
or (os_name != 'Linux' and sys_platform == 'win32' and python_version == '3.7') \
or (os_name != 'Linux' and sys_platform == 'win32' and python_version == '3.8')",
"(python_full_version < '3.7' and os_name == 'Linux' and sys_platform == 'win32') \
or (python_full_version >= '3.9' and os_name == 'Linux' and sys_platform == 'win32') \
or (python_full_version >= '3.7' and python_full_version < '3.9' and sys_platform == 'win32')",
"(sys_platform == 'win32' and python_full_version >= '3.7' and python_full_version < '3.9') or (os_name == 'Linux' and sys_platform == 'win32')",
);

assert_simplifies(
Expand All @@ -2716,8 +2523,8 @@ mod test {
assert_simplifies(
"(os_name == 'nt' and sys_platform == 'win32') \
or (os_name != 'nt' and platform_version == '1' and (sys_platform == 'win32' or sys_platform == 'win64'))",
"(platform_version == '1' and sys_platform == 'win32') \
or (os_name != 'nt' and platform_version == '1' and sys_platform == 'win64') \
"(sys_platform == 'win32' and platform_version == '1') \
or (os_name != 'nt' and sys_platform == 'win64' and platform_version == '1') \
or (os_name == 'nt' and sys_platform == 'win32')",
);

Expand Down
1 change: 1 addition & 0 deletions crates/uv-resolver/src/resolution/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ impl ResolverOutput {
// Insert each node only once.
continue;
}

Self::add_edge(&mut graph, &mut inverse, root_index, edge, marker.clone());
}
}
Expand Down
18 changes: 9 additions & 9 deletions crates/uv/tests/it/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,36 +439,36 @@ fn dependency_multiple_markers() -> Result<()> {
# This file was autogenerated by uv via the following command:
# uv export --cache-dir [CACHE_DIR]
-e .
attrs==23.2.0 ; sys_platform == 'win32' or python_full_version >= '3.12' \
attrs==23.2.0 ; python_full_version >= '3.12' or sys_platform == 'win32' \
--hash=sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30 \
--hash=sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1
cffi==1.16.0 ; (implementation_name != 'pypy' and os_name == 'nt' and sys_platform == 'win32') or (python_full_version >= '3.12' and implementation_name != 'pypy' and os_name == 'nt') \
cffi==1.16.0 ; (os_name == 'nt' and implementation_name != 'pypy' and python_full_version >= '3.12') or (os_name == 'nt' and sys_platform == 'win32' and implementation_name != 'pypy') \
--hash=sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab \
--hash=sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235 \
--hash=sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a \
--hash=sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b \
--hash=sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0 \
--hash=sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba \
--hash=sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1
exceptiongroup==1.2.0 ; python_full_version < '3.11' and sys_platform == 'win32' \
exceptiongroup==1.2.0 ; sys_platform == 'win32' and python_full_version < '3.11' \
--hash=sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14 \
--hash=sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68
idna==3.6 ; sys_platform == 'win32' or python_full_version >= '3.12' \
idna==3.6 ; python_full_version >= '3.12' or sys_platform == 'win32' \
--hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \
--hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f
outcome==1.3.0.post0 ; sys_platform == 'win32' or python_full_version >= '3.12' \
outcome==1.3.0.post0 ; python_full_version >= '3.12' or sys_platform == 'win32' \
--hash=sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8 \
--hash=sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b
pycparser==2.21 ; (implementation_name != 'pypy' and os_name == 'nt' and sys_platform == 'win32') or (python_full_version >= '3.12' and implementation_name != 'pypy' and os_name == 'nt') \
pycparser==2.21 ; (os_name == 'nt' and implementation_name != 'pypy' and python_full_version >= '3.12') or (os_name == 'nt' and sys_platform == 'win32' and implementation_name != 'pypy') \
--hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \
--hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206
sniffio==1.3.1 ; sys_platform == 'win32' or python_full_version >= '3.12' \
sniffio==1.3.1 ; python_full_version >= '3.12' or sys_platform == 'win32' \
--hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \
--hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc
sortedcontainers==2.4.0 ; sys_platform == 'win32' or python_full_version >= '3.12' \
sortedcontainers==2.4.0 ; python_full_version >= '3.12' or sys_platform == 'win32' \
--hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \
--hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0
trio==0.25.0 ; sys_platform == 'win32' or python_full_version >= '3.12' \
trio==0.25.0 ; python_full_version >= '3.12' or sys_platform == 'win32' \
--hash=sha256:9b41f5993ad2c0e5f62d0acca320ec657fdb6b2a2c22b8c7aed6caf154475c4e \
--hash=sha256:e6458efe29cc543e557a91e614e2b51710eba2961669329ce9c862d50c6e8e81
Expand Down
Loading

0 comments on commit 7580a50

Please sign in to comment.