aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-semver/src/constraint
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-23 23:14:52 +0900
committernsfisis <nsfisis@gmail.com>2026-05-23 23:15:14 +0900
commitdbdecaf5a1c54a876b7ee0153d58dd39b1080f97 (patch)
treef13f2ced03c803dcbc42a5672458b3cb19ff0f30 /crates/shirabe-semver/src/constraint
parentf5b987a00712211b7ce56300851182bda904e97b (diff)
downloadphp-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/constraint')
-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
6 files changed, 286 insertions, 336 deletions
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)
}
}