aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-semver/src/intervals.rs
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/intervals.rs
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/intervals.rs')
-rw-r--r--crates/shirabe-semver/src/intervals.rs249
1 files changed, 114 insertions, 135 deletions
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(),
})