aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-semver/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe-semver/src')
-rw-r--r--crates/shirabe-semver/src/comparator.rs9
-rw-r--r--crates/shirabe-semver/src/compiling_matcher.rs23
-rw-r--r--crates/shirabe-semver/src/constraint.rs8
-rw-r--r--crates/shirabe-semver/src/constraint/any_constraint.rs169
-rw-r--r--crates/shirabe-semver/src/constraint/constraint_interface.rs43
-rw-r--r--crates/shirabe-semver/src/constraint/match_all_constraint.rs55
-rw-r--r--crates/shirabe-semver/src/constraint/match_none_constraint.rs41
-rw-r--r--crates/shirabe-semver/src/constraint/multi_constraint.rs196
-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.rs25
-rw-r--r--crates/shirabe-semver/src/intervals.rs249
-rw-r--r--crates/shirabe-semver/src/semver.rs10
-rw-r--r--crates/shirabe-semver/src/version_parser.rs83
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,
+ ))]);
}
}