diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-23 23:14:52 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-23 23:15:14 +0900 |
| commit | dbdecaf5a1c54a876b7ee0153d58dd39b1080f97 (patch) | |
| tree | f13f2ced03c803dcbc42a5672458b3cb19ff0f30 /crates/shirabe-semver/src | |
| parent | f5b987a00712211b7ce56300851182bda904e97b (diff) | |
| download | php-shirabe-dbdecaf5a1c54a876b7ee0153d58dd39b1080f97.tar.gz php-shirabe-dbdecaf5a1c54a876b7ee0153d58dd39b1080f97.tar.zst php-shirabe-dbdecaf5a1c54a876b7ee0153d58dd39b1080f97.zip | |
refactor(semver): change ConstraintInterface to a closed enum
Replace the dyn ConstraintInterface trait objects with an AnyConstraint
enum closing over its four implementors (Simple, Multi, MatchAll,
MatchNone), mirroring the earlier Rule enum conversion. Rename
constraint.rs to simple_constraint.rs to match the renamed Constraint
type.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-semver/src')
| -rw-r--r-- | crates/shirabe-semver/src/comparator.rs | 9 | ||||
| -rw-r--r-- | crates/shirabe-semver/src/compiling_matcher.rs | 23 | ||||
| -rw-r--r-- | crates/shirabe-semver/src/constraint.rs | 8 | ||||
| -rw-r--r-- | crates/shirabe-semver/src/constraint/any_constraint.rs | 169 | ||||
| -rw-r--r-- | crates/shirabe-semver/src/constraint/constraint_interface.rs | 43 | ||||
| -rw-r--r-- | crates/shirabe-semver/src/constraint/match_all_constraint.rs | 55 | ||||
| -rw-r--r-- | crates/shirabe-semver/src/constraint/match_none_constraint.rs | 41 | ||||
| -rw-r--r-- | crates/shirabe-semver/src/constraint/multi_constraint.rs | 196 | ||||
| -rw-r--r-- | crates/shirabe-semver/src/constraint/simple_constraint.rs (renamed from crates/shirabe-semver/src/constraint/constraint.rs) | 118 | ||||
| -rw-r--r-- | crates/shirabe-semver/src/interval.rs | 25 | ||||
| -rw-r--r-- | crates/shirabe-semver/src/intervals.rs | 249 | ||||
| -rw-r--r-- | crates/shirabe-semver/src/semver.rs | 10 | ||||
| -rw-r--r-- | crates/shirabe-semver/src/version_parser.rs | 83 |
13 files changed, 483 insertions, 546 deletions
diff --git a/crates/shirabe-semver/src/comparator.rs b/crates/shirabe-semver/src/comparator.rs index 6db06d4..36a4142 100644 --- a/crates/shirabe-semver/src/comparator.rs +++ b/crates/shirabe-semver/src/comparator.rs @@ -1,6 +1,6 @@ //! ref: composer/vendor/composer/semver/src/Comparator.php -use crate::constraint::Constraint; +use crate::constraint::SimpleConstraint; pub struct Comparator; @@ -30,7 +30,10 @@ impl Comparator { } pub fn compare(version1: String, operator: String, version2: String) -> bool { - let constraint = Constraint::new(operator, version2); - constraint.match_specific(&Constraint::new("==".to_string(), version1), true) + let constraint = SimpleConstraint::new(operator, version2, None); + constraint.match_specific( + &SimpleConstraint::new("==".to_string(), version1, None), + true, + ) } } diff --git a/crates/shirabe-semver/src/compiling_matcher.rs b/crates/shirabe-semver/src/compiling_matcher.rs index 737647f..d7e203e 100644 --- a/crates/shirabe-semver/src/compiling_matcher.rs +++ b/crates/shirabe-semver/src/compiling_matcher.rs @@ -5,8 +5,8 @@ use std::sync::OnceLock; use indexmap::IndexMap; -use crate::constraint::Constraint; -use crate::constraint::ConstraintInterface; +use crate::constraint::AnyConstraint; +use crate::constraint::SimpleConstraint; static COMPILED_CHECKER_CACHE: OnceLock< Mutex<IndexMap<String, Box<dyn Fn(String, bool) -> bool + Send + Sync>>>, @@ -16,12 +16,12 @@ static RESULT_CACHE: OnceLock<Mutex<IndexMap<String, bool>>> = OnceLock::new(); // Rust does not support eval(), so the compiled checker path is always disabled. // The COMPILED_CHECKER_CACHE is retained structurally but never populated. static TRANS_OP_INT: &[(i64, &str)] = &[ - (Constraint::OP_EQ, Constraint::STR_OP_EQ), - (Constraint::OP_LT, Constraint::STR_OP_LT), - (Constraint::OP_LE, Constraint::STR_OP_LE), - (Constraint::OP_GT, Constraint::STR_OP_GT), - (Constraint::OP_GE, Constraint::STR_OP_GE), - (Constraint::OP_NE, Constraint::STR_OP_NE), + (SimpleConstraint::OP_EQ, SimpleConstraint::STR_OP_EQ), + (SimpleConstraint::OP_LT, SimpleConstraint::STR_OP_LT), + (SimpleConstraint::OP_LE, SimpleConstraint::STR_OP_LE), + (SimpleConstraint::OP_GT, SimpleConstraint::STR_OP_GT), + (SimpleConstraint::OP_GE, SimpleConstraint::STR_OP_GE), + (SimpleConstraint::OP_NE, SimpleConstraint::STR_OP_NE), ]; pub struct CompilingMatcher; @@ -41,8 +41,8 @@ impl CompilingMatcher { Self::compiled_checker_cache().lock().unwrap().clear(); } - pub fn r#match(constraint: &dyn ConstraintInterface, operator: i64, version: String) -> bool { - let result_cache_key = format!("{}{};{}", operator, constraint.__to_string(), version); + pub fn r#match(constraint: &AnyConstraint, operator: i64, version: String) -> bool { + let result_cache_key = format!("{}{};{}", operator, constraint.to_string(), version); { let cache = Self::result_cache().lock().unwrap(); @@ -56,7 +56,8 @@ impl CompilingMatcher { .find(|(op, _)| *op == operator) .map(|(_, s)| *s) .expect("unknown operator"); - let result = constraint.matches(&Constraint::new(trans_op.to_string(), version)); + let result = + constraint.matches(&SimpleConstraint::new(trans_op.to_string(), version, None).into()); Self::result_cache() .lock() diff --git a/crates/shirabe-semver/src/constraint.rs b/crates/shirabe-semver/src/constraint.rs index 6a57a57..75c15b6 100644 --- a/crates/shirabe-semver/src/constraint.rs +++ b/crates/shirabe-semver/src/constraint.rs @@ -1,13 +1,13 @@ +mod any_constraint; mod bound; -mod constraint; -mod constraint_interface; mod match_all_constraint; mod match_none_constraint; mod multi_constraint; +mod simple_constraint; +pub use any_constraint::*; pub use bound::*; -pub use constraint::*; -pub use constraint_interface::*; pub use match_all_constraint::*; pub use match_none_constraint::*; pub use multi_constraint::*; +pub use simple_constraint::*; diff --git a/crates/shirabe-semver/src/constraint/any_constraint.rs b/crates/shirabe-semver/src/constraint/any_constraint.rs new file mode 100644 index 0000000..b61a36f --- /dev/null +++ b/crates/shirabe-semver/src/constraint/any_constraint.rs @@ -0,0 +1,169 @@ +//! ref: composer/vendor/composer/semver/src/Constraint/ConstraintInterface.php + +use crate::constraint::Bound; +use crate::constraint::MatchAllConstraint; +use crate::constraint::MatchNoneConstraint; +use crate::constraint::MultiConstraint; +use crate::constraint::SimpleConstraint; + +/// Corresponds to PHP's `ConstraintInterface`. +#[derive(Clone, Debug)] +pub enum AnyConstraint { + Simple(SimpleConstraint), + Multi(MultiConstraint), + MatchAll(MatchAllConstraint), + MatchNone(MatchNoneConstraint), +} + +impl AnyConstraint { + pub fn matches(&self, provider: &AnyConstraint) -> bool { + match self { + Self::MatchAll(_) => true, + Self::MatchNone(_) => false, + Self::Simple(c) => match provider.as_constraint() { + Some(p) => c.match_specific(p, false), + None => provider.matches(self), + }, + Self::Multi(m) => { + if !m.conjunctive { + m.constraints.iter().any(|sub| provider.matches(sub)) + } else if provider.is_disjunctive() { + provider.matches(self) + } else { + m.constraints.iter().all(|sub| provider.matches(sub)) + } + } + } + } + + pub fn compile(&self, other_operator: i64) -> String { + match self { + Self::Simple(c) => c.compile(other_operator), + Self::Multi(c) => c.compile(other_operator), + Self::MatchAll(c) => c.compile(other_operator), + Self::MatchNone(c) => c.compile(other_operator), + } + } + + pub fn get_upper_bound(&self) -> Bound { + match self { + Self::Simple(c) => c.get_upper_bound(), + Self::Multi(c) => c.get_upper_bound(), + Self::MatchAll(c) => c.get_upper_bound(), + Self::MatchNone(c) => c.get_upper_bound(), + } + } + + pub fn get_lower_bound(&self) -> Bound { + match self { + Self::Simple(c) => c.get_lower_bound(), + Self::Multi(c) => c.get_lower_bound(), + Self::MatchAll(c) => c.get_lower_bound(), + Self::MatchNone(c) => c.get_lower_bound(), + } + } + + pub fn get_pretty_string(&self) -> String { + match self { + Self::Simple(c) => c.get_pretty_string(), + Self::Multi(c) => c.get_pretty_string(), + Self::MatchAll(c) => c.get_pretty_string(), + Self::MatchNone(c) => c.get_pretty_string(), + } + } + + /// PHP `$c instanceof MultiConstraint && $c->isDisjunctive()`. + pub fn is_disjunctive(&self) -> bool { + matches!(self, Self::Multi(m) if !m.conjunctive) + } + + /// PHP `$c instanceof Constraint`. + pub fn is_constraint(&self) -> bool { + matches!(self, Self::Simple(_)) + } + + pub fn get_operator(&self) -> &'static str { + match self { + Self::Simple(c) => c.get_operator(), + _ => "", + } + } + + pub fn get_version(&self) -> &str { + match self { + Self::Simple(c) => c.get_version(), + _ => "", + } + } + + pub fn as_constraint(&self) -> Option<&SimpleConstraint> { + match self { + Self::Simple(c) => Some(c), + _ => None, + } + } + + pub fn as_multi_constraint(&self) -> Option<&MultiConstraint> { + match self { + Self::Multi(c) => Some(c), + _ => None, + } + } + + pub fn is_match_all(&self) -> bool { + matches!(self, Self::MatchAll(_)) + } + + pub fn is_match_none(&self) -> bool { + matches!(self, Self::MatchNone(_)) + } + + /// PHP exposes `ConstraintInterface::setPrettyString()` and defaults the + /// pretty string to the constraint's string form when unset. This port takes + /// the pretty string at construction instead; this setter exists only so + /// `MultiConstraint::create()` can apply the pretty string PHP sets on its + /// (possibly polymorphic) result. + pub(crate) fn set_pretty_string(&mut self, pretty_string: Option<String>) { + match self { + Self::Simple(c) => c.pretty_string = pretty_string, + Self::Multi(c) => c.pretty_string = pretty_string, + Self::MatchAll(c) => c.pretty_string = pretty_string, + Self::MatchNone(c) => c.pretty_string = pretty_string, + } + } +} + +impl From<SimpleConstraint> for AnyConstraint { + fn from(c: SimpleConstraint) -> Self { + Self::Simple(c) + } +} + +impl From<MultiConstraint> for AnyConstraint { + fn from(c: MultiConstraint) -> Self { + Self::Multi(c) + } +} + +impl From<MatchAllConstraint> for AnyConstraint { + fn from(c: MatchAllConstraint) -> Self { + Self::MatchAll(c) + } +} + +impl From<MatchNoneConstraint> for AnyConstraint { + fn from(c: MatchNoneConstraint) -> Self { + Self::MatchNone(c) + } +} + +impl std::fmt::Display for AnyConstraint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Simple(c) => write!(f, "{}", c), + Self::Multi(c) => write!(f, "{}", c), + Self::MatchAll(c) => write!(f, "{}", c), + Self::MatchNone(c) => write!(f, "{}", c), + } + } +} diff --git a/crates/shirabe-semver/src/constraint/constraint_interface.rs b/crates/shirabe-semver/src/constraint/constraint_interface.rs deleted file mode 100644 index f04ed2a..0000000 --- a/crates/shirabe-semver/src/constraint/constraint_interface.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! ref: composer/vendor/composer/semver/src/Constraint/ConstraintInterface.php - -use crate::constraint::Bound; - -pub trait ConstraintInterface: std::fmt::Debug { - fn matches(&self, provider: &dyn ConstraintInterface) -> bool; - - fn compile(&self, other_operator: i64) -> String; - - fn get_upper_bound(&self) -> Bound; - - fn get_lower_bound(&self) -> Bound; - - fn get_pretty_string(&self) -> String; - - fn set_pretty_string(&mut self, pretty_string: Option<String>); - - fn __to_string(&self) -> String; - - // Rust-specific helpers for instanceof checks in MultiConstraint::matches and optimizeConstraints. - fn is_disjunctive(&self) -> bool { - false - } - - /// Rust-specific helper: PHP `$c instanceof Constraint` check. - fn is_constraint(&self) -> bool { - false - } - - /// Rust-specific helper: PHP `$c->getOperator()`. Only meaningful when `is_constraint()` is true. - fn get_operator(&self) -> &'static str { - "" - } - - /// Rust-specific helper: PHP `$c->getVersion()`. Only meaningful when `is_constraint()` is true. - fn get_version(&self) -> &str { - "" - } - - fn clone_box(&self) -> Box<dyn ConstraintInterface>; - - fn as_any(&self) -> &dyn std::any::Any; -} diff --git a/crates/shirabe-semver/src/constraint/match_all_constraint.rs b/crates/shirabe-semver/src/constraint/match_all_constraint.rs index b695ce2..8d4e870 100644 --- a/crates/shirabe-semver/src/constraint/match_all_constraint.rs +++ b/crates/shirabe-semver/src/constraint/match_all_constraint.rs @@ -1,68 +1,41 @@ //! ref: composer/vendor/composer/semver/src/Constraint/MatchAllConstraint.php use crate::constraint::Bound; -use crate::constraint::ConstraintInterface; -#[derive(Debug)] +#[derive(Debug, Clone, Default)] pub struct MatchAllConstraint { pub(crate) pretty_string: Option<String>, } impl MatchAllConstraint { - pub fn new() -> Self { - Self { - pretty_string: None, - } + pub fn new(pretty_string: Option<String>) -> Self { + Self { pretty_string } } -} - -impl Default for MatchAllConstraint { - fn default() -> Self { - Self::new() - } -} -impl ConstraintInterface for MatchAllConstraint { - fn matches(&self, _provider: &dyn ConstraintInterface) -> bool { - true - } - - fn compile(&self, _other_operator: i64) -> String { + pub fn compile(&self, _other_operator: i64) -> String { "true".to_string() } - fn set_pretty_string(&mut self, pretty_string: Option<String>) { - self.pretty_string = pretty_string; - } - - fn get_pretty_string(&self) -> String { + pub fn get_pretty_string(&self) -> String { if let Some(ref s) = self.pretty_string && !s.is_empty() { return s.clone(); } - self.__to_string() + self.to_string() } - fn __to_string(&self) -> String { - "*".to_string() - } - - fn clone_box(&self) -> Box<dyn ConstraintInterface> { - Box::new(MatchAllConstraint { - pretty_string: self.pretty_string.clone(), - }) - } - - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn get_upper_bound(&self) -> Bound { + pub fn get_upper_bound(&self) -> Bound { Bound::positive_infinity() } - fn get_lower_bound(&self) -> Bound { + pub fn get_lower_bound(&self) -> Bound { Bound::zero() } } + +impl std::fmt::Display for MatchAllConstraint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "*") + } +} diff --git a/crates/shirabe-semver/src/constraint/match_none_constraint.rs b/crates/shirabe-semver/src/constraint/match_none_constraint.rs index 587058a..08cb319 100644 --- a/crates/shirabe-semver/src/constraint/match_none_constraint.rs +++ b/crates/shirabe-semver/src/constraint/match_none_constraint.rs @@ -1,54 +1,41 @@ //! ref: composer/vendor/composer/semver/src/Constraint/MatchNoneConstraint.php use crate::constraint::Bound; -use crate::constraint::ConstraintInterface; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct MatchNoneConstraint { pub(crate) pretty_string: Option<String>, } -impl ConstraintInterface for MatchNoneConstraint { - fn matches(&self, _provider: &dyn ConstraintInterface) -> bool { - false +impl MatchNoneConstraint { + pub fn new(pretty_string: Option<String>) -> Self { + Self { pretty_string } } - fn compile(&self, _other_operator: i64) -> String { + pub fn compile(&self, _other_operator: i64) -> String { "false".to_string() } - fn set_pretty_string(&mut self, pretty_string: Option<String>) { - self.pretty_string = pretty_string; - } - - fn get_pretty_string(&self) -> String { + pub fn get_pretty_string(&self) -> String { if let Some(ref s) = self.pretty_string && !s.is_empty() { return s.clone(); } - self.__to_string() + self.to_string() } - fn __to_string(&self) -> String { - "[]".to_string() - } - - fn clone_box(&self) -> Box<dyn ConstraintInterface> { - Box::new(MatchNoneConstraint { - pretty_string: self.pretty_string.clone(), - }) - } - - fn as_any(&self) -> &dyn std::any::Any { - self + pub fn get_upper_bound(&self) -> Bound { + Bound::new("0.0.0.0-dev".to_string(), false) } - fn get_upper_bound(&self) -> Bound { + pub fn get_lower_bound(&self) -> Bound { Bound::new("0.0.0.0-dev".to_string(), false) } +} - fn get_lower_bound(&self) -> Bound { - Bound::new("0.0.0.0-dev".to_string(), false) +impl std::fmt::Display for MatchNoneConstraint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "[]") } } diff --git a/crates/shirabe-semver/src/constraint/multi_constraint.rs b/crates/shirabe-semver/src/constraint/multi_constraint.rs index 4222095..05cc16e 100644 --- a/crates/shirabe-semver/src/constraint/multi_constraint.rs +++ b/crates/shirabe-semver/src/constraint/multi_constraint.rs @@ -1,30 +1,22 @@ //! ref: composer/vendor/composer/semver/src/Constraint/MultiConstraint.php -use std::cell::RefCell; - +use crate::constraint::AnyConstraint; use crate::constraint::Bound; -use crate::constraint::ConstraintInterface; use crate::constraint::MatchAllConstraint; +#[derive(Debug, Clone)] pub struct MultiConstraint { - pub(crate) constraints: Vec<Box<dyn ConstraintInterface>>, + pub(crate) constraints: Vec<AnyConstraint>, pub(crate) pretty_string: Option<String>, - string: RefCell<Option<String>>, pub(crate) conjunctive: bool, - lower_bound: RefCell<Option<Bound>>, - upper_bound: RefCell<Option<Bound>>, -} - -impl std::fmt::Debug for MultiConstraint { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("MultiConstraint") - .field("conjunctive", &self.conjunctive) - .finish() - } } impl MultiConstraint { - pub fn new(constraints: Vec<Box<dyn ConstraintInterface>>, conjunctive: bool) -> Self { + pub fn new( + constraints: Vec<AnyConstraint>, + conjunctive: bool, + pretty_string: Option<String>, + ) -> Self { assert!( constraints.len() >= 2, "Must provide at least two constraints for a MultiConstraint. Use \ @@ -34,15 +26,12 @@ impl MultiConstraint { Self { constraints, - pretty_string: None, - string: RefCell::new(None), + pretty_string, conjunctive, - lower_bound: RefCell::new(None), - upper_bound: RefCell::new(None), } } - pub fn get_constraints(&self) -> &[Box<dyn ConstraintInterface>] { + pub fn get_constraints(&self) -> &[AnyConstraint] { &self.constraints } @@ -54,11 +43,9 @@ impl MultiConstraint { !self.conjunctive } - fn extract_bounds(&self) { - if self.lower_bound.borrow().is_some() { - return; - } - + /// Composer memoizes the result; this port recomputes on every call. It is not heavy + /// calculation so caching is a premature optimization. + fn extract_bounds(&self) -> (Bound, Bound) { let mut current_lower: Option<Bound> = None; let mut current_upper: Option<Bound> = None; @@ -93,52 +80,61 @@ impl MultiConstraint { } } - *self.lower_bound.borrow_mut() = current_lower; - *self.upper_bound.borrow_mut() = current_upper; + ( + current_lower.expect("MultiConstraint always has at least two constraints"), + current_upper.expect("MultiConstraint always has at least two constraints"), + ) } pub fn create( - constraints: Vec<Box<dyn ConstraintInterface>>, + constraints: Vec<AnyConstraint>, conjunctive: bool, - ) -> anyhow::Result<Box<dyn ConstraintInterface>> { + pretty_string: Option<String>, + ) -> anyhow::Result<AnyConstraint> { if constraints.is_empty() { - return Ok(Box::new(MatchAllConstraint { - pretty_string: None, - })); + return Ok(MatchAllConstraint::new(pretty_string).into()); } if constraints.len() == 1 { - return Ok(constraints.into_iter().next().unwrap()); + let mut single = constraints.into_iter().next().unwrap(); + if pretty_string.is_some() { + single.set_pretty_string(pretty_string); + } + return Ok(single); } let (constraints, conjunctive) = Self::optimize_constraints(constraints, conjunctive); if constraints.len() == 1 { - return Ok(constraints.into_iter().next().unwrap()); + let mut single = constraints.into_iter().next().unwrap(); + if pretty_string.is_some() { + single.set_pretty_string(pretty_string); + } + return Ok(single); } - Ok(Box::new(MultiConstraint::new(constraints, conjunctive))) + Ok(MultiConstraint::new(constraints, conjunctive, pretty_string).into()) } // Returns the (possibly optimized) constraints and the effective conjunctive flag. // Always returns the constraints vector (consuming it), whether or not optimization was applied. // The PHP version returns null for no optimization; here we return the original values unchanged. fn optimize_constraints( - constraints: Vec<Box<dyn ConstraintInterface>>, + constraints: Vec<AnyConstraint>, conjunctive: bool, - ) -> (Vec<Box<dyn ConstraintInterface>>, bool) { + ) -> (Vec<AnyConstraint>, bool) { // Parse the two OR groups and if they are contiguous collapse into one constraint. // [>= 1 < 2] || [>= 2 < 3] || [>= 3 < 4] => [>= 1 < 4] if !conjunctive { let mut iter = constraints.into_iter(); - let mut left: Box<dyn ConstraintInterface> = iter.next().unwrap(); - let mut merged_constraints: Vec<Box<dyn ConstraintInterface>> = Vec::new(); + let mut left: AnyConstraint = iter.next().unwrap(); + let mut merged_constraints: Vec<AnyConstraint> = Vec::new(); let mut optimized = false; for right in iter { - let merged: Option<Box<dyn ConstraintInterface>> = { - let maybe_l_mc = left.as_any().downcast_ref::<MultiConstraint>(); - let maybe_r_mc = right.as_any().downcast_ref::<MultiConstraint>(); + let merged: Option<AnyConstraint> = { + let maybe_l_mc = left.as_multi_constraint(); + let maybe_r_mc = right.as_multi_constraint(); if let (Some(l_mc), Some(r_mc)) = (maybe_l_mc, maybe_r_mc) { if l_mc.conjunctive @@ -146,10 +142,10 @@ impl MultiConstraint { && l_mc.constraints.len() == 2 && r_mc.constraints.len() == 2 { - let left0 = l_mc.constraints[0].__to_string(); - let left1 = l_mc.constraints[1].__to_string(); - let right0 = r_mc.constraints[0].__to_string(); - let right1 = r_mc.constraints[1].__to_string(); + let left0 = l_mc.constraints[0].to_string(); + let left1 = l_mc.constraints[1].to_string(); + let right0 = r_mc.constraints[0].to_string(); + let right1 = r_mc.constraints[1].to_string(); if left0.starts_with(">=") && left1.starts_with('<') @@ -157,14 +153,17 @@ impl MultiConstraint { && right1.starts_with('<') && left1.get(2..) == right0.get(3..) { - Some(Box::new(MultiConstraint::new( - vec![ - l_mc.constraints[0].clone_box(), - r_mc.constraints[1].clone_box(), - ], - true, - )) - as Box<dyn ConstraintInterface>) + Some( + MultiConstraint::new( + vec![ + l_mc.constraints[0].clone(), + r_mc.constraints[1].clone(), + ], + true, + None, + ) + .into(), + ) } else { None } @@ -198,10 +197,8 @@ impl MultiConstraint { (constraints, conjunctive) } -} -impl ConstraintInterface for MultiConstraint { - fn compile(&self, other_operator: i64) -> String { + pub fn compile(&self, other_operator: i64) -> String { let mut parts = Vec::new(); for constraint in &self.constraints { let code = constraint.compile(other_operator); @@ -233,87 +230,28 @@ impl ConstraintInterface for MultiConstraint { } } - fn matches(&self, provider: &dyn ConstraintInterface) -> bool { - if !self.conjunctive { - for constraint in &self.constraints { - if provider.matches(constraint.as_ref()) { - return true; - } - } - return false; - } - - if provider.is_disjunctive() { - return provider.matches(self); - } - - for constraint in &self.constraints { - if !provider.matches(constraint.as_ref()) { - return false; - } - } - - true - } - - fn set_pretty_string(&mut self, pretty_string: Option<String>) { - self.pretty_string = pretty_string; - } - - fn get_pretty_string(&self) -> String { + pub fn get_pretty_string(&self) -> String { if let Some(ref s) = self.pretty_string && !s.is_empty() { return s.clone(); } - self.__to_string() - } - - fn __to_string(&self) -> String { - if let Some(ref s) = *self.string.borrow() { - return s.clone(); - } - - let parts: Vec<String> = self.constraints.iter().map(|c| c.__to_string()).collect(); - let sep = if self.conjunctive { " " } else { " || " }; - let result = format!("[{}]", parts.join(sep)); - - *self.string.borrow_mut() = Some(result.clone()); - result - } - - fn get_lower_bound(&self) -> Bound { - self.extract_bounds(); - self.lower_bound - .borrow() - .clone() - .expect("extractBounds should have populated the lowerBound property") - } - - fn get_upper_bound(&self) -> Bound { - self.extract_bounds(); - self.upper_bound - .borrow() - .clone() - .expect("extractBounds should have populated the upperBound property") + self.to_string() } - fn is_disjunctive(&self) -> bool { - !self.conjunctive + pub fn get_lower_bound(&self) -> Bound { + self.extract_bounds().0 } - fn clone_box(&self) -> Box<dyn ConstraintInterface> { - Box::new(MultiConstraint { - constraints: self.constraints.iter().map(|c| c.clone_box()).collect(), - pretty_string: self.pretty_string.clone(), - string: RefCell::new(self.string.borrow().clone()), - conjunctive: self.conjunctive, - lower_bound: RefCell::new(self.lower_bound.borrow().clone()), - upper_bound: RefCell::new(self.upper_bound.borrow().clone()), - }) + pub fn get_upper_bound(&self) -> Bound { + self.extract_bounds().1 } +} - fn as_any(&self) -> &dyn std::any::Any { - self +impl std::fmt::Display for MultiConstraint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let parts: Vec<String> = self.constraints.iter().map(|c| c.to_string()).collect(); + let sep = if self.conjunctive { " " } else { " || " }; + write!(f, "[{}]", parts.join(sep)) } } diff --git a/crates/shirabe-semver/src/constraint/constraint.rs b/crates/shirabe-semver/src/constraint/simple_constraint.rs index 3ac633d..161425a 100644 --- a/crates/shirabe-semver/src/constraint/constraint.rs +++ b/crates/shirabe-semver/src/constraint/simple_constraint.rs @@ -1,23 +1,19 @@ //! ref: composer/vendor/composer/semver/src/Constraint/Constraint.php -use std::sync::Mutex; - use anyhow::bail; use shirabe_php_shim as php; use crate::constraint::Bound; -use crate::constraint::ConstraintInterface; -#[derive(Debug)] -pub struct Constraint { +/// Corresponds to PHP's `Constraint`. +#[derive(Debug, Clone)] +pub struct SimpleConstraint { pub(crate) operator: i64, pub(crate) version: String, pub(crate) pretty_string: Option<String>, - pub(crate) lower_bound: Mutex<Option<Bound>>, - pub(crate) upper_bound: Mutex<Option<Bound>>, } -impl Constraint { +impl SimpleConstraint { pub const OP_EQ: i64 = 0; pub const OP_LT: i64 = 1; pub const OP_LE: i64 = 2; @@ -60,8 +56,7 @@ impl Constraint { } } - pub fn new(operator: impl Into<String>, version: impl Into<String>) -> Self { - let operator: String = operator.into(); + pub fn new(operator: String, version: String, pretty_string: Option<String>) -> Self { let op_int = Self::trans_op_str(&operator).unwrap_or_else(|| { // PHP raises InvalidArgumentException; in the Rust port keep that as a panic // because invalid operators are programmer errors caught during porting. @@ -74,10 +69,8 @@ impl Constraint { Self { operator: op_int, - version: version.into(), - pretty_string: None, - lower_bound: Mutex::new(None), - upper_bound: Mutex::new(None), + version, + pretty_string, } } @@ -225,7 +218,7 @@ impl Constraint { format!("!$b && {}", code_comparison) } - pub fn match_specific(&self, provider: &Constraint, compare_branches: bool) -> bool { + pub fn match_specific(&self, provider: &SimpleConstraint, compare_branches: bool) -> bool { let no_equal_op = Self::trans_op_int(self.operator).replace('=', ""); let provider_no_equal_op = Self::trans_op_int(provider.operator).replace('=', ""); @@ -286,18 +279,14 @@ impl Constraint { false } - fn extract_bounds(&self) { - if self.lower_bound.lock().unwrap().is_some() { - return; - } - + /// Composer memoizes the result; this port recomputes on every call. It is not heavy + /// calculation so caching is a premature optimization. + fn extract_bounds(&self) -> (Bound, Bound) { if self.version.starts_with("dev-") { - *self.lower_bound.lock().unwrap() = Some(Bound::zero()); - *self.upper_bound.lock().unwrap() = Some(Bound::positive_infinity()); - return; + return (Bound::zero(), Bound::positive_infinity()); } - let (lower, upper) = match self.operator { + match self.operator { Self::OP_EQ => ( Bound::new(self.version.clone(), true), Bound::new(self.version.clone(), true), @@ -314,96 +303,33 @@ impl Constraint { ), Self::OP_NE => (Bound::zero(), Bound::positive_infinity()), _ => panic!("unknown operator: {}", self.operator), - }; - - *self.lower_bound.lock().unwrap() = Some(lower); - *self.upper_bound.lock().unwrap() = Some(upper); - } -} - -impl Clone for Constraint { - fn clone(&self) -> Self { - Self { - operator: self.operator, - version: self.version.clone(), - pretty_string: self.pretty_string.clone(), - lower_bound: Mutex::new(self.lower_bound.lock().unwrap().clone()), - upper_bound: Mutex::new(self.upper_bound.lock().unwrap().clone()), } } -} -impl ConstraintInterface for Constraint { - fn matches(&self, provider: &dyn ConstraintInterface) -> bool { - if let Some(p) = provider.as_any().downcast_ref::<Constraint>() { - return self.match_specific(p, false); - } - provider.matches(self) - } - - fn compile(&self, other_operator: i64) -> String { + pub fn compile(&self, other_operator: i64) -> String { self.compile_constraint(other_operator) } - fn set_pretty_string(&mut self, pretty_string: Option<String>) { - self.pretty_string = pretty_string; - } - - fn get_pretty_string(&self) -> String { + pub fn get_pretty_string(&self) -> String { if let Some(ref s) = self.pretty_string && !s.is_empty() { return s.clone(); } - self.__to_string() + self.to_string() } - fn __to_string(&self) -> String { - format!("{} {}", Self::trans_op_int(self.operator), self.version) + pub fn get_lower_bound(&self) -> Bound { + self.extract_bounds().0 } - fn get_lower_bound(&self) -> Bound { - self.extract_bounds(); - self.lower_bound - .lock() - .unwrap() - .clone() - .expect("extract_bounds should have set lower_bound") - } - - fn get_upper_bound(&self) -> Bound { - self.extract_bounds(); - self.upper_bound - .lock() - .unwrap() - .clone() - .expect("extract_bounds should have set upper_bound") - } - - fn clone_box(&self) -> Box<dyn ConstraintInterface> { - Box::new(self.clone()) - } - - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn is_constraint(&self) -> bool { - true - } - - fn get_operator(&self) -> &'static str { - Self::trans_op_int(self.operator) - } - - fn get_version(&self) -> &str { - &self.version + pub fn get_upper_bound(&self) -> Bound { + self.extract_bounds().1 } } -impl std::fmt::Display for Constraint { +impl std::fmt::Display for SimpleConstraint { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use crate::constraint::ConstraintInterface; - write!(f, "{}", ConstraintInterface::__to_string(self)) + write!(f, "{} {}", Self::trans_op_int(self.operator), self.version) } } diff --git a/crates/shirabe-semver/src/interval.rs b/crates/shirabe-semver/src/interval.rs index b1f3010..7f27514 100644 --- a/crates/shirabe-semver/src/interval.rs +++ b/crates/shirabe-semver/src/interval.rs @@ -1,8 +1,6 @@ //! ref: composer/vendor/composer/semver/src/Interval.php -use std::sync::OnceLock; - -use crate::constraint::Constraint; +use crate::constraint::SimpleConstraint; #[derive(Debug, Clone)] pub struct DevConstraintSet { @@ -12,32 +10,29 @@ pub struct DevConstraintSet { #[derive(Debug, Clone)] pub struct Interval { - start: Constraint, - end: Constraint, + start: SimpleConstraint, + end: SimpleConstraint, } impl Interval { - pub fn new(start: Constraint, end: Constraint) -> Self { + pub fn new(start: SimpleConstraint, end: SimpleConstraint) -> Self { Self { start, end } } - pub fn get_start(&self) -> &Constraint { + pub fn get_start(&self) -> &SimpleConstraint { &self.start } - pub fn get_end(&self) -> &Constraint { + pub fn get_end(&self) -> &SimpleConstraint { &self.end } - pub fn from_zero() -> &'static Constraint { - static ZERO: OnceLock<Constraint> = OnceLock::new(); - ZERO.get_or_init(|| Constraint::new(">=".to_string(), "0.0.0.0-dev".to_string())) + pub fn from_zero() -> SimpleConstraint { + SimpleConstraint::new(">=".to_string(), "0.0.0.0-dev".to_string(), None) } - pub fn until_positive_infinity() -> &'static Constraint { - static POSITIVE_INFINITY: OnceLock<Constraint> = OnceLock::new(); - POSITIVE_INFINITY - .get_or_init(|| Constraint::new("<".to_string(), format!("{}.0.0.0", i64::MAX))) + pub fn until_positive_infinity() -> SimpleConstraint { + SimpleConstraint::new("<".to_string(), format!("{}.0.0.0", i64::MAX), None) } pub fn any() -> Self { diff --git a/crates/shirabe-semver/src/intervals.rs b/crates/shirabe-semver/src/intervals.rs index 72231b6..68b23bf 100644 --- a/crates/shirabe-semver/src/intervals.rs +++ b/crates/shirabe-semver/src/intervals.rs @@ -3,11 +3,11 @@ use std::collections::HashMap; use std::sync::{Mutex, OnceLock}; -use crate::constraint::Constraint; -use crate::constraint::ConstraintInterface; +use crate::constraint::AnyConstraint; use crate::constraint::MatchAllConstraint; use crate::constraint::MatchNoneConstraint; use crate::constraint::MultiConstraint; +use crate::constraint::SimpleConstraint; use crate::interval::{DevConstraintSet, Interval}; use shirabe_php_shim as php; @@ -42,31 +42,19 @@ impl Intervals { } pub fn is_subset_of( - candidate: &dyn ConstraintInterface, - constraint: &dyn ConstraintInterface, + candidate: &AnyConstraint, + constraint: &AnyConstraint, ) -> anyhow::Result<bool> { - if constraint - .as_any() - .downcast_ref::<MatchAllConstraint>() - .is_some() - { + if constraint.is_match_all() { return Ok(true); } - if candidate - .as_any() - .downcast_ref::<MatchNoneConstraint>() - .is_some() - || constraint - .as_any() - .downcast_ref::<MatchNoneConstraint>() - .is_some() - { + if candidate.is_match_none() || constraint.is_match_none() { return Ok(false); } - // Phase B: ConstraintInterface needs clone_box() to create owned copies from references. - let multi = MultiConstraint::new(vec![candidate.clone_box(), constraint.clone_box()], true); + let multi = + MultiConstraint::new(vec![candidate.clone(), constraint.clone()], true, None).into(); let intersection_intervals = Self::get(&multi)?; let candidate_intervals = Self::get(candidate)?; @@ -79,14 +67,14 @@ impl Intervals { return Ok(false); } - if candidate_intervals.numeric[index].get_start().__to_string() - != interval.get_start().__to_string() + if candidate_intervals.numeric[index].get_start().to_string() + != interval.get_start().to_string() { return Ok(false); } - if candidate_intervals.numeric[index].get_end().__to_string() - != interval.get_end().__to_string() + if candidate_intervals.numeric[index].get_end().to_string() + != interval.get_end().to_string() { return Ok(false); } @@ -107,24 +95,16 @@ impl Intervals { Ok(true) } - pub fn have_intersections( - a: &dyn ConstraintInterface, - b: &dyn ConstraintInterface, - ) -> anyhow::Result<bool> { - if a.as_any().downcast_ref::<MatchAllConstraint>().is_some() - || b.as_any().downcast_ref::<MatchAllConstraint>().is_some() - { + pub fn have_intersections(a: &AnyConstraint, b: &AnyConstraint) -> anyhow::Result<bool> { + if a.is_match_all() || b.is_match_all() { return Ok(true); } - if a.as_any().downcast_ref::<MatchNoneConstraint>().is_some() - || b.as_any().downcast_ref::<MatchNoneConstraint>().is_some() - { + if a.is_match_none() || b.is_match_none() { return Ok(false); } - // Phase B: ConstraintInterface needs clone_box(). - let multi = MultiConstraint::new(vec![a.clone_box(), b.clone_box()], true); + let multi = MultiConstraint::new(vec![a.clone(), b.clone()], true, None).into(); let intersection_intervals = Self::generate_intervals(&multi, true)?; Ok(!intersection_intervals.numeric.is_empty() @@ -132,30 +112,24 @@ impl Intervals { || !intersection_intervals.branches.names.is_empty()) } - pub fn compact_constraint( - constraint: &dyn ConstraintInterface, - ) -> anyhow::Result<Box<dyn ConstraintInterface>> { - if constraint - .as_any() - .downcast_ref::<MultiConstraint>() - .is_none() - { - return Ok(constraint.clone_box()); + pub fn compact_constraint(constraint: &AnyConstraint) -> anyhow::Result<AnyConstraint> { + if constraint.as_multi_constraint().is_none() { + return Ok(constraint.clone()); } let intervals = Self::generate_intervals(constraint, false)?; - let mut constraints: Vec<Box<dyn ConstraintInterface>> = Vec::new(); + let mut constraints: Vec<AnyConstraint> = Vec::new(); let mut has_numeric_match_all = false; if intervals.numeric.len() == 1 - && intervals.numeric[0].get_start().__to_string() == Interval::from_zero().__to_string() - && intervals.numeric[0].get_end().__to_string() - == Interval::until_positive_infinity().__to_string() + && intervals.numeric[0].get_start().to_string() == Interval::from_zero().to_string() + && intervals.numeric[0].get_end().to_string() + == Interval::until_positive_infinity().to_string() { - constraints.push(Box::new(intervals.numeric[0].get_start().clone())); + constraints.push(intervals.numeric[0].get_start().clone().into()); has_numeric_match_all = true; } else { - let mut un_equal_constraints: Vec<Box<dyn ConstraintInterface>> = Vec::new(); + let mut un_equal_constraints: Vec<AnyConstraint> = Vec::new(); let count = intervals.numeric.len(); let mut i = 0; while i < count { @@ -176,15 +150,18 @@ impl Intervals { // unEqualConstraints currently contains [>=M, !=N] already and we only // want to add !=P right now if un_equal_constraints.is_empty() - && interval.get_start().__to_string() - != Interval::from_zero().__to_string() + && interval.get_start().to_string() != Interval::from_zero().to_string() { - un_equal_constraints.push(Box::new(interval.get_start().clone())); + un_equal_constraints.push(interval.get_start().clone().into()); } - un_equal_constraints.push(Box::new(Constraint::new( - "!=".to_string(), - interval.get_end().get_version().to_string(), - ))); + un_equal_constraints.push( + SimpleConstraint::new( + "!=".to_string(), + interval.get_end().get_version().to_string(), + None, + ) + .into(), + ); i += 1; continue; } @@ -192,16 +169,16 @@ impl Intervals { if !un_equal_constraints.is_empty() { // this is where the end of the following interval of a != constraint is added - if interval.get_end().__to_string() - != Interval::until_positive_infinity().__to_string() + if interval.get_end().to_string() + != Interval::until_positive_infinity().to_string() { - un_equal_constraints.push(Box::new(interval.get_end().clone())); + un_equal_constraints.push(interval.get_end().clone().into()); } // count is 1 if entire constraint is just one != expression if un_equal_constraints.len() > 1 { constraints - .push(Box::new(MultiConstraint::new(un_equal_constraints, true))); + .push(MultiConstraint::new(un_equal_constraints, true, None).into()); } else { constraints.push(un_equal_constraints.into_iter().next().unwrap()); } @@ -216,55 +193,59 @@ impl Intervals { && interval.get_start().get_operator() == ">=" && interval.get_end().get_operator() == "<=" { - constraints.push(Box::new(Constraint::new( - "==".to_string(), - interval.get_start().get_version().to_string(), - ))); + constraints.push( + SimpleConstraint::new( + "==".to_string(), + interval.get_start().get_version().to_string(), + None, + ) + .into(), + ); i += 1; continue; } - if interval.get_start().__to_string() == Interval::from_zero().__to_string() { - constraints.push(Box::new(interval.get_end().clone())); - } else if interval.get_end().__to_string() - == Interval::until_positive_infinity().__to_string() + if interval.get_start().to_string() == Interval::from_zero().to_string() { + constraints.push(interval.get_end().clone().into()); + } else if interval.get_end().to_string() + == Interval::until_positive_infinity().to_string() { - constraints.push(Box::new(interval.get_start().clone())); + constraints.push(interval.get_start().clone().into()); } else { - constraints.push(Box::new(MultiConstraint::new( - vec![ - Box::new(interval.get_start().clone()), - Box::new(interval.get_end().clone()), - ], - true, - ))); + constraints.push( + MultiConstraint::new( + vec![ + AnyConstraint::Simple(interval.get_start().clone()), + AnyConstraint::Simple(interval.get_end().clone()), + ], + true, + None, + ) + .into(), + ); } i += 1; } } - let mut dev_constraints: Vec<Box<dyn ConstraintInterface>> = Vec::new(); + let mut dev_constraints: Vec<AnyConstraint> = Vec::new(); if intervals.branches.names.is_empty() { if intervals.branches.exclude && has_numeric_match_all { - return Ok(Box::new(MatchAllConstraint { - pretty_string: None, - })); + return Ok(MatchAllConstraint::new(None).into()); // otherwise constraint should contain a != operator and already cover this } } else { for branch_name in &intervals.branches.names { if intervals.branches.exclude { - dev_constraints.push(Box::new(Constraint::new( - "!=".to_string(), - branch_name.clone(), - ))); + dev_constraints.push( + SimpleConstraint::new("!=".to_string(), branch_name.clone(), None).into(), + ); } else { - dev_constraints.push(Box::new(Constraint::new( - "==".to_string(), - branch_name.clone(), - ))); + dev_constraints.push( + SimpleConstraint::new("==".to_string(), branch_name.clone(), None).into(), + ); } } @@ -272,26 +253,25 @@ impl Intervals { // > 2.0 != dev-foo must return a conjunctive constraint if intervals.branches.exclude { if constraints.len() > 1 { - let merged: Vec<Box<dyn ConstraintInterface>> = - std::iter::once(Box::new(MultiConstraint::new(constraints, false)) - as Box<dyn ConstraintInterface>) - .chain(dev_constraints) - .collect(); - return Ok(Box::new(MultiConstraint::new(merged, true))); + let merged: Vec<AnyConstraint> = + std::iter::once(MultiConstraint::new(constraints, false, None).into()) + .chain(dev_constraints) + .collect(); + return Ok(MultiConstraint::new(merged, true, None).into()); } if constraints.len() == 1 - && constraints[0].__to_string() == Interval::from_zero().__to_string() + && constraints[0].to_string() == Interval::from_zero().to_string() { if dev_constraints.len() > 1 { - return Ok(Box::new(MultiConstraint::new(dev_constraints, true))); + return Ok(MultiConstraint::new(dev_constraints, true, None).into()); } return Ok(dev_constraints.into_iter().next().unwrap()); } - let merged: Vec<Box<dyn ConstraintInterface>> = + let merged: Vec<AnyConstraint> = constraints.into_iter().chain(dev_constraints).collect(); - return Ok(Box::new(MultiConstraint::new(merged, true))); + return Ok(MultiConstraint::new(merged, true, None).into()); } // otherwise devConstraints contains a list of == operators for branches which are @@ -300,20 +280,18 @@ impl Intervals { } if constraints.len() > 1 { - return Ok(Box::new(MultiConstraint::new(constraints, false))); + return Ok(MultiConstraint::new(constraints, false, None).into()); } if constraints.len() == 1 { return Ok(constraints.into_iter().next().unwrap()); } - Ok(Box::new(MatchNoneConstraint { - pretty_string: None, - })) + Ok(MatchNoneConstraint::new(None).into()) } - pub fn get(constraint: &dyn ConstraintInterface) -> anyhow::Result<IntervalCollection> { - let key = constraint.__to_string(); + pub fn get(constraint: &AnyConstraint) -> anyhow::Result<IntervalCollection> { + let key = constraint.to_string(); { let cache = intervals_cache().lock().unwrap(); @@ -333,14 +311,10 @@ impl Intervals { } fn generate_intervals( - constraint: &dyn ConstraintInterface, + constraint: &AnyConstraint, stop_on_first_valid_interval: bool, ) -> anyhow::Result<IntervalCollection> { - if constraint - .as_any() - .downcast_ref::<MatchAllConstraint>() - .is_some() - { + if constraint.is_match_all() { return Ok(IntervalCollection { numeric: vec![Interval::new( Interval::from_zero().clone(), @@ -350,37 +324,30 @@ impl Intervals { }); } - if constraint - .as_any() - .downcast_ref::<MatchNoneConstraint>() - .is_some() - { + if constraint.is_match_none() { return Ok(IntervalCollection { numeric: vec![], branches: Interval::no_dev(), }); } - if let Some(c) = constraint.as_any().downcast_ref::<Constraint>() { + if let Some(c) = constraint.as_constraint() { return Self::generate_single_constraint_intervals(c); } - let multi = constraint - .as_any() - .downcast_ref::<MultiConstraint>() - .ok_or_else(|| { - anyhow::anyhow!( - "The constraint passed in should be an MatchAllConstraint, Constraint or \ + let multi = constraint.as_multi_constraint().ok_or_else(|| { + anyhow::anyhow!( + "The constraint passed in should be an MatchAllConstraint, Constraint or \ MultiConstraint instance, got an unknown type." - ) - })?; + ) + })?; let sub_constraints = multi.get_constraints(); let mut numeric_groups: Vec<Vec<Interval>> = Vec::new(); let mut constraint_branches: Vec<DevConstraintSet> = Vec::new(); for c in sub_constraints { - let res = Self::get(c.as_ref())?; + let res = Self::get(c)?; numeric_groups.push(res.numeric); constraint_branches.push(res.branches); } @@ -494,7 +461,7 @@ impl Intervals { } else { 1 }; - let mut start: Option<Constraint> = None; + let mut start: Option<SimpleConstraint> = None; for (version, operator, is_start) in &borders { if *is_start { @@ -504,7 +471,11 @@ impl Intervals { } if start.is_none() && active_intervals >= activation_threshold { - start = Some(Constraint::new(operator.clone(), version.clone())); + start = Some(SimpleConstraint::new( + operator.clone(), + version.clone(), + None, + )); } else if start.is_some() && active_intervals < activation_threshold { let start_c = start.take().unwrap(); // filter out invalid intervals like > x - <= x, or >= x - < x @@ -516,7 +487,7 @@ impl Intervals { } else { intervals.push(Interval::new( start_c, - Constraint::new(operator.clone(), version.clone()), + SimpleConstraint::new(operator.clone(), version.clone(), None), )); if stop_on_first_valid_interval { @@ -533,7 +504,7 @@ impl Intervals { } fn generate_single_constraint_intervals( - constraint: &Constraint, + constraint: &SimpleConstraint, ) -> anyhow::Result<IntervalCollection> { let op = constraint.get_operator(); @@ -589,10 +560,18 @@ impl Intervals { numeric: vec![ Interval::new( Interval::from_zero().clone(), - Constraint::new("<".to_string(), constraint.get_version().to_string()), + SimpleConstraint::new( + "<".to_string(), + constraint.get_version().to_string(), + None, + ), ), Interval::new( - Constraint::new(">".to_string(), constraint.get_version().to_string()), + SimpleConstraint::new( + ">".to_string(), + constraint.get_version().to_string(), + None, + ), Interval::until_positive_infinity().clone(), ), ], @@ -603,8 +582,8 @@ impl Intervals { // convert ==x to an interval of >=x - <=x Ok(IntervalCollection { numeric: vec![Interval::new( - Constraint::new(">=".to_string(), constraint.get_version().to_string()), - Constraint::new("<=".to_string(), constraint.get_version().to_string()), + SimpleConstraint::new(">=".to_string(), constraint.get_version().to_string(), None), + SimpleConstraint::new("<=".to_string(), constraint.get_version().to_string(), None), )], branches: Interval::no_dev(), }) diff --git a/crates/shirabe-semver/src/semver.rs b/crates/shirabe-semver/src/semver.rs index 62f50b6..2314069 100644 --- a/crates/shirabe-semver/src/semver.rs +++ b/crates/shirabe-semver/src/semver.rs @@ -3,7 +3,8 @@ use std::sync::OnceLock; use crate::comparator::Comparator; -use crate::constraint::Constraint; +use crate::constraint::AnyConstraint; +use crate::constraint::SimpleConstraint; use crate::version_parser::VersionParser; pub struct Semver; @@ -19,7 +20,12 @@ impl Semver { pub fn satisfies(version: String, constraints: String) -> anyhow::Result<bool> { let version_parser = Self::version_parser(); - let provider = Constraint::new("==".to_string(), version_parser.normalize(&version, None)?); + let provider = SimpleConstraint::new( + "==".to_string(), + version_parser.normalize(&version, None)?, + None, + ) + .into(); let parsed_constraints = version_parser.parse_constraints(&constraints)?; Ok(parsed_constraints.matches(&provider)) } diff --git a/crates/shirabe-semver/src/version_parser.rs b/crates/shirabe-semver/src/version_parser.rs index ba2b59d..bbd359e 100644 --- a/crates/shirabe-semver/src/version_parser.rs +++ b/crates/shirabe-semver/src/version_parser.rs @@ -1,9 +1,9 @@ //! ref: composer/vendor/composer/semver/src/VersionParser.php -use crate::constraint::Constraint; -use crate::constraint::ConstraintInterface; +use crate::constraint::AnyConstraint; use crate::constraint::MatchAllConstraint; use crate::constraint::MultiConstraint; +use crate::constraint::SimpleConstraint; use shirabe_php_shim as php; // Regex to match pre-release data (sort of). @@ -294,16 +294,13 @@ impl VersionParser { name.to_string() } - pub fn parse_constraints( - &self, - constraints: &str, - ) -> anyhow::Result<Box<dyn ConstraintInterface>> { + pub fn parse_constraints(&self, constraints: &str) -> anyhow::Result<AnyConstraint> { let pretty_constraint = constraints.to_string(); let or_constraints = php::preg_split("{\\s*\\|\\|?\\s*}", &php::trim(constraints, None)) .ok_or_else(|| anyhow::anyhow!("Failed to preg_split string: {}", constraints))?; - let mut or_groups: Vec<Box<dyn ConstraintInterface>> = Vec::new(); + let mut or_groups: Vec<AnyConstraint> = Vec::new(); for or_constraint in &or_constraints { let and_constraints = php::preg_split( @@ -312,9 +309,8 @@ impl VersionParser { ) .ok_or_else(|| anyhow::anyhow!("Failed to preg_split string: {}", or_constraint))?; - let constraint_objects: Vec<Box<dyn ConstraintInterface>> = if and_constraints.len() > 1 - { - let mut objs: Vec<Box<dyn ConstraintInterface>> = Vec::new(); + let constraint_objects: Vec<AnyConstraint> = if and_constraints.len() > 1 { + let mut objs: Vec<AnyConstraint> = Vec::new(); for and_constraint in &and_constraints { for parsed in self.parse_constraint(and_constraint)? { objs.push(parsed); @@ -325,26 +321,21 @@ impl VersionParser { self.parse_constraint(&and_constraints[0])? }; - let constraint: Box<dyn ConstraintInterface> = if constraint_objects.len() == 1 { + let constraint: AnyConstraint = if constraint_objects.len() == 1 { constraint_objects.into_iter().next().unwrap() } else { - Box::new(MultiConstraint::new(constraint_objects, true)) + MultiConstraint::new(constraint_objects, true, None).into() }; or_groups.push(constraint); } - let mut parsed_constraint = MultiConstraint::create(or_groups, false)?; - - parsed_constraint.set_pretty_string(Some(pretty_constraint)); - - Ok(parsed_constraint) + // PHP sets the pretty string on the create() result via setPrettyString(); + // the port threads it through create() instead (no setter). + MultiConstraint::create(or_groups, false, Some(pretty_constraint)) } - fn parse_constraint( - &self, - constraint: &str, - ) -> anyhow::Result<Vec<Box<dyn ConstraintInterface>>> { + fn parse_constraint(&self, constraint: &str) -> anyhow::Result<Vec<AnyConstraint>> { let mut constraint = constraint.to_string(); // strip off aliasing @@ -399,15 +390,14 @@ impl VersionParser { .unwrap_or("") .is_empty(); if m1_nonempty || m2_nonempty { - return Ok(vec![Box::new(Constraint::new( + return Ok(vec![AnyConstraint::Simple(SimpleConstraint::new( ">=".to_string(), "0.0.0.0-dev".to_string(), + None, ))]); } - return Ok(vec![Box::new(MatchAllConstraint { - pretty_string: None, - })]); + return Ok(vec![AnyConstraint::MatchAll(MatchAllConstraint::new(None))]); } let version_regex = format!( @@ -461,7 +451,7 @@ impl VersionParser { let low_version = self.normalize(&format!("{}{}", &constraint[1..], stability_suffix), None)?; - let lower_bound = Constraint::new(">=".to_string(), low_version); + let lower_bound = SimpleConstraint::new(">=".to_string(), low_version, None); // For upper bound, we increment the position of one more significance, // but highPosition = 0 would be illegal @@ -471,9 +461,12 @@ impl VersionParser { self.manipulate_version_string(&matches, high_position, 1, "0") .unwrap_or_default() ); - let upper_bound = Constraint::new("<".to_string(), high_version); + let upper_bound = SimpleConstraint::new("<".to_string(), high_version, None); - return Ok(vec![Box::new(lower_bound), Box::new(upper_bound)]); + return Ok(vec![ + AnyConstraint::Simple(lower_bound), + AnyConstraint::Simple(upper_bound), + ]); } // Caret Range @@ -508,7 +501,7 @@ impl VersionParser { let low_version = self.normalize(&format!("{}{}", &constraint[1..], stability_suffix), None)?; - let lower_bound = Constraint::new(">=".to_string(), low_version); + let lower_bound = SimpleConstraint::new(">=".to_string(), low_version, None); // For upper bound, we increment the position of one more significance, // but highPosition = 0 would be illegal @@ -517,9 +510,12 @@ impl VersionParser { self.manipulate_version_string(&matches, position, 1, "0") .unwrap_or_default() ); - let upper_bound = Constraint::new("<".to_string(), high_version); + let upper_bound = SimpleConstraint::new("<".to_string(), high_version, None); - return Ok(vec![Box::new(lower_bound), Box::new(upper_bound)]); + return Ok(vec![ + AnyConstraint::Simple(lower_bound), + AnyConstraint::Simple(upper_bound), + ]); } // X Range @@ -554,15 +550,16 @@ impl VersionParser { ); if low_version == "0.0.0.0-dev" { - return Ok(vec![Box::new(Constraint::new( + return Ok(vec![AnyConstraint::Simple(SimpleConstraint::new( "<".to_string(), high_version, + None, ))]); } return Ok(vec![ - Box::new(Constraint::new(">=".to_string(), low_version)), - Box::new(Constraint::new("<".to_string(), high_version)), + AnyConstraint::Simple(SimpleConstraint::new(">=".to_string(), low_version, None)), + AnyConstraint::Simple(SimpleConstraint::new("<".to_string(), high_version, None)), ]); } @@ -593,9 +590,10 @@ impl VersionParser { let from_str = matches[1].clone().unwrap_or_default(); // matches['from'] let low_version = self.normalize(&from_str, None)?; - let lower_bound = Constraint::new( + let lower_bound = SimpleConstraint::new( ">=".to_string(), format!("{}{}", low_version, low_stability_suffix), + None, ); // PHP's empty() on "0" returns true, but here we only check for truly empty/missing @@ -603,14 +601,14 @@ impl VersionParser { // matches[12]=to minor, matches[13]=to patch, matches[15]=to stability, // matches[17]=to dev, matches[18]=to wildcard-dev - let upper_bound: Constraint = if (!empty(&matches[12]) && !empty(&matches[13])) + let upper_bound: SimpleConstraint = if (!empty(&matches[12]) && !empty(&matches[13])) || !matches[15].as_deref().unwrap_or("").is_empty() || !matches[17].as_deref().unwrap_or("").is_empty() || !matches[18].as_deref().unwrap_or("").is_empty() { let to_str = matches[10].clone().unwrap_or_default(); // matches['to'] let hv = self.normalize(&to_str, None)?; - Constraint::new("<=".to_string(), hv) + SimpleConstraint::new("<=".to_string(), hv, None) } else { // matches[11]=to major, matches[12]=to minor, matches[13]=to patch, // matches[14]=to fourth @@ -632,10 +630,13 @@ impl VersionParser { self.manipulate_version_string(&high_match, position, 1, "0") .unwrap_or_default() ); - Constraint::new("<".to_string(), hv) + SimpleConstraint::new("<".to_string(), hv, None) }; - return Ok(vec![Box::new(lower_bound), Box::new(upper_bound)]); + return Ok(vec![ + AnyConstraint::Simple(lower_bound), + AnyConstraint::Simple(upper_bound), + ]); } // Basic Comparators @@ -691,7 +692,9 @@ impl VersionParser { } else { op_str }; - return Ok(vec![Box::new(Constraint::new(final_op, version))]); + return Ok(vec![AnyConstraint::Simple(SimpleConstraint::new( + final_op, version, None, + ))]); } } |
