Skip to main content

rez_next_version/
range.rs

1//! Version range implementation - full rez-compatible version range parsing
2
3use super::Version;
4use rez_next_common::RezCoreError;
5use serde::{Deserialize, Serialize};
6
7/// A single bound in a version range
8#[derive(Clone, Debug, PartialEq, Eq)]
9enum Bound {
10    /// >= version
11    Ge(Version),
12    /// > version
13    Gt(Version),
14    /// <= version
15    Le(Version),
16    /// < version
17    Lt(Version),
18    /// == version (exact match)
19    Eq(Version),
20    /// != version
21    Ne(Version),
22    /// ~= version (compatible release: >= version AND < next major.minor)
23    Compatible(Version),
24    /// Any version (no constraint)
25    Any,
26    /// Empty set (no versions match)
27    None,
28}
29
30/// A conjunction of bounds (all must be satisfied)
31#[derive(Clone, Debug, PartialEq, Eq)]
32struct BoundSet {
33    bounds: Vec<Bound>,
34}
35
36impl BoundSet {
37    fn any() -> Self {
38        BoundSet {
39            bounds: vec![Bound::Any],
40        }
41    }
42
43    fn none() -> Self {
44        BoundSet {
45            bounds: vec![Bound::None],
46        }
47    }
48
49    fn contains(&self, version: &Version) -> bool {
50        for bound in &self.bounds {
51            if !bound_matches(bound, version) {
52                return false;
53            }
54        }
55        true
56    }
57}
58
59fn bound_matches(bound: &Bound, version: &Version) -> bool {
60    match bound {
61        Bound::Any => true,
62        Bound::None => false,
63        Bound::Ge(v) => version >= v,
64        Bound::Gt(v) => version > v,
65        Bound::Le(v) => version <= v,
66        Bound::Lt(v) => version < v,
67        Bound::Eq(v) => version == v,
68        Bound::Ne(v) => version != v,
69        Bound::Compatible(v) => {
70            // ~= M.N means >= M.N AND < M.(N+1), or ~= M.N.P means >= M.N.P AND < M.N+1
71            // For rez we implement as: >= v AND same prefix up to second-to-last component
72            if version < v {
73                return false;
74            }
75            // Compatible release: upper bound is next minor/patch
76            let parts = v.as_str().split('.').collect::<Vec<_>>();
77            if parts.len() < 2 {
78                return true;
79            }
80            let prefix = &parts[..parts.len() - 1].join(".");
81            // version must start with same prefix
82            version.as_str().starts_with(&format!("{}.", prefix))
83                || version.as_str() == prefix.as_str()
84        }
85    }
86}
87
88/// Version range representation - a disjunction of BoundSets (union of intersections)
89#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
90pub struct VersionRange {
91    /// Cached string representation
92    pub range_str: String,
93    /// Parsed bound sets (disjunction - any must match)
94    #[serde(skip)]
95    bound_sets: Vec<BoundSet>,
96    /// Whether the range was successfully parsed
97    #[serde(skip)]
98    is_parsed: bool,
99    /// Ranges to subtract (for set difference operations)
100    #[serde(skip)]
101    subtract_from: Vec<VersionRange>,
102}
103
104impl VersionRange {
105    /// Create a new version range from a string
106    pub fn new(range_str: String) -> Result<Self, RezCoreError> {
107        Self::parse(&range_str)
108    }
109
110    /// Create a version range that matches any version (equivalent to `""` or `"*"`)
111    pub fn any() -> Self {
112        VersionRange {
113            range_str: String::new(),
114            bound_sets: vec![BoundSet::any()],
115            is_parsed: true,
116            subtract_from: Vec::new(),
117        }
118    }
119
120    /// Create a version range that matches no version (empty set)
121    pub fn none() -> Self {
122        VersionRange {
123            range_str: "!*".to_string(),
124            bound_sets: vec![BoundSet::none()],
125            is_parsed: true,
126            subtract_from: Vec::new(),
127        }
128    }
129
130    /// Parse a version range string
131    ///
132    /// Supported formats:
133    /// - `""` or `"*"` - any version
134    /// - `">=1.0"` - single constraint
135    /// - `">=1.0,<2.0"` - comma-separated AND constraints (rez style)
136    /// - `">=1.0 <2.0"` - space-separated AND constraints
137    /// - `"1.0+"` - rez shorthand for `>=1.0`
138    /// - `"<1.0|>=2.0"` - pipe-separated OR constraints
139    /// - `"==1.0"` - exact version
140    /// - `"~=1.4"` - compatible release
141    pub fn parse(range_str: &str) -> Result<Self, RezCoreError> {
142        let trimmed = range_str.trim();
143        let bound_sets = parse_range_str(trimmed)?;
144        Ok(VersionRange {
145            range_str: range_str.to_string(),
146            bound_sets,
147            is_parsed: true,
148            subtract_from: Vec::new(),
149        })
150    }
151
152    /// Check if a version satisfies this range
153    pub fn contains(&self, version: &Version) -> bool {
154        if !self.is_parsed || self.bound_sets.is_empty() {
155            return true;
156        }
157        // Disjunction: any bound_set matching means the version is included
158        let in_self = self.bound_sets.iter().any(|bs| bs.contains(version));
159        if !in_self {
160            return false;
161        }
162        // Subtract: if version is in any subtract range, exclude it
163        for sub_range in &self.subtract_from {
164            if sub_range.contains(version) {
165                return false;
166            }
167        }
168        true
169    }
170
171    /// Get the string representation
172    pub fn as_str(&self) -> &str {
173        &self.range_str
174    }
175
176    /// Check if this range intersects with another range
177    pub fn intersects(&self, other: &VersionRange) -> bool {
178        // Conservative: if either is "any", they intersect
179        if self.is_any() || other.is_any() {
180            return true;
181        }
182        // Check if the ranges have any overlap by checking bounds interaction
183        // For exact versions, check containment
184        for bs_self in &self.bound_sets {
185            for bs_other in &other.bound_sets {
186                // Check if these two bound sets can co-exist
187                if bound_sets_intersect(bs_self, bs_other) {
188                    return true;
189                }
190            }
191        }
192        false
193    }
194
195    /// Compute the intersection of two ranges
196    pub fn intersect(&self, other: &VersionRange) -> Option<VersionRange> {
197        if self.is_any() {
198            return Some(other.clone());
199        }
200        if other.is_any() {
201            return Some(self.clone());
202        }
203        // Merge all bound sets with AND semantics
204        // Only include merged sets that are satisfiable (not trivially empty)
205        let mut result_sets = Vec::new();
206        for bs_self in &self.bound_sets {
207            for bs_other in &other.bound_sets {
208                let mut merged = bs_self.bounds.clone();
209                merged.extend(bs_other.bounds.clone());
210                let merged_set = BoundSet { bounds: merged };
211                // Only include if this merged set is satisfiable
212                if is_bound_set_satisfiable(&merged_set) {
213                    result_sets.push(merged_set);
214                }
215            }
216        }
217
218        if result_sets.is_empty() {
219            return None;
220        }
221        let new_str = format!("({})&({})", self.range_str, other.range_str);
222        let mut combined_subtracts = self.subtract_from.clone();
223        combined_subtracts.extend(other.subtract_from.clone());
224        Some(VersionRange {
225            range_str: new_str,
226            bound_sets: result_sets,
227            is_parsed: true,
228            subtract_from: combined_subtracts,
229        })
230    }
231
232    /// Compute the union of two ranges (pipe-separated)
233    pub fn union(&self, other: &VersionRange) -> VersionRange {
234        let new_str = format!("{}|{}", self.range_str, other.range_str);
235        let mut sets = self.bound_sets.clone();
236        sets.extend(other.bound_sets.clone());
237        VersionRange {
238            range_str: new_str,
239            bound_sets: sets,
240            is_parsed: true,
241            subtract_from: Vec::new(),
242        }
243    }
244
245    /// Compute the difference of two ranges: versions in self but not in other
246    /// Returns None if the result would be empty
247    pub fn subtract(&self, other: &VersionRange) -> Option<VersionRange> {
248        if other.is_any() {
249            return None; // self - any = empty
250        }
251        if other.is_empty() {
252            return Some(self.clone());
253        }
254        if self.is_empty() {
255            return None;
256        }
257        // Use subtract_from field: self with other excluded via contains() check
258        let new_str = format!("({})-({})", self.range_str, other.range_str);
259        let mut subtracts = self.subtract_from.clone();
260        subtracts.push(other.clone());
261        let range = VersionRange {
262            range_str: new_str,
263            bound_sets: self.bound_sets.clone(),
264            is_parsed: true,
265            subtract_from: subtracts,
266        };
267        // Quick sanity: at least one probe version must be in the result
268        let probes = self.collect_probe_versions_with_other(other);
269        let has_any = probes.iter().any(|v| range.contains(v));
270        if has_any {
271            Some(range)
272        } else {
273            None
274        }
275    }
276
277    /// Check if this range is the "any" range (matches all versions)
278    pub fn is_any(&self) -> bool {
279        let s = self.range_str.trim();
280        if s.is_empty() || s == "*" {
281            return true;
282        }
283        // Check if all bound sets are Any
284        self.bound_sets
285            .iter()
286            .all(|bs| bs.bounds.is_empty() || bs.bounds.iter().all(|b| matches!(b, Bound::Any)))
287    }
288
289    /// Check if this range is a subset of another range
290    /// (every version in self is also in other)
291    pub fn is_subset_of(&self, other: &VersionRange) -> bool {
292        if other.is_any() {
293            return true;
294        }
295        if self.is_any() {
296            return other.is_any();
297        }
298        if self.is_empty() {
299            return true;
300        }
301        let probe_versions = self.collect_probe_versions_with_other(other);
302        for v in &probe_versions {
303            if self.contains(v) && !other.contains(v) {
304                return false;
305            }
306        }
307        true
308    }
309
310    /// Check if this range is a superset of another range
311    pub fn is_superset_of(&self, other: &VersionRange) -> bool {
312        other.is_subset_of(self)
313    }
314
315    /// Collect probe versions from both self and other's bounds, plus "beyond" versions
316    fn collect_probe_versions_with_other(&self, other: &VersionRange) -> Vec<Version> {
317        let mut versions = Vec::new();
318        for range in [self as &VersionRange, other] {
319            for bs in &range.bound_sets {
320                for bound in &bs.bounds {
321                    match bound {
322                        Bound::Ge(v)
323                        | Bound::Gt(v)
324                        | Bound::Le(v)
325                        | Bound::Lt(v)
326                        | Bound::Eq(v)
327                        | Bound::Ne(v)
328                        | Bound::Compatible(v) => {
329                            versions.push(v.clone());
330                            if let Ok(bumped) = Version::parse(&format!("{}.999999", v.as_str())) {
331                                versions.push(bumped);
332                            }
333                        }
334                        _ => {}
335                    }
336                }
337            }
338        }
339        for s in &["0.0.1", "999.999.999"] {
340            if let Ok(v) = Version::parse(s) {
341                versions.push(v);
342            }
343        }
344        versions
345    }
346
347    /// Check if this range is empty (no versions match)
348    pub fn is_empty(&self) -> bool {
349        let s = self.range_str.trim();
350        if s == "empty" || s == "!*" {
351            return true;
352        }
353        if self.is_parsed && !self.bound_sets.is_empty() {
354            return self
355                .bound_sets
356                .iter()
357                .all(|bs| bs.bounds.iter().any(|b| matches!(b, Bound::None)));
358        }
359        false
360    }
361}
362
363/// Parse a range string into a vector of BoundSets (disjunction)
364fn parse_range_str(s: &str) -> Result<Vec<BoundSet>, RezCoreError> {
365    if s.is_empty() || s == "*" {
366        return Ok(vec![BoundSet::any()]);
367    }
368    if s == "empty" || s == "!*" {
369        return Ok(vec![BoundSet::none()]);
370    }
371
372    // Handle rez ".." interval syntax: "1.0..2.0" = ">=1.0,<2.0"
373    // Note: must check before splitting on |
374    if s.contains("..") && !s.starts_with('.') {
375        // Only handle if it's a simple "a..b" form (no | or other operators in the whole string)
376        if let Some(dot_pos) = s.find("..") {
377            let left = &s[..dot_pos];
378            let right = &s[dot_pos + 2..];
379            // Both sides must look like version strings (not empty operators)
380            if !left.is_empty()
381                && !right.is_empty()
382                && !left.starts_with('>')
383                && !left.starts_with('<')
384                && !left.starts_with('=')
385                && !left.starts_with('!')
386                && !left.starts_with('~')
387            {
388                // "left..right" -> ">=left,<right"
389                let new_s = format!(">={},<{}", left.trim(), right.trim());
390                return parse_range_str(&new_s);
391            }
392        }
393    }
394
395    // Split on | for OR (union)
396    let or_parts: Vec<&str> = s.split('|').collect();
397    let mut result = Vec::new();
398
399    for or_part in or_parts {
400        let bound_set = parse_conjunction(or_part.trim())?;
401        result.push(bound_set);
402    }
403
404    Ok(result)
405}
406
407/// Parse a conjunction of constraints (AND semantics)
408/// Supports: `>=1.0,<2.0` (comma) or `>=1.0 <2.0` (space) or `1.0+<2.0` (rez syntax)
409fn parse_conjunction(s: &str) -> Result<BoundSet, RezCoreError> {
410    if s.is_empty() || s == "*" {
411        return Ok(BoundSet::any());
412    }
413
414    // Handle rez shorthand: "1.0+" = ">=1.0"
415    // Handle rez shorthand: "1.0+<2.0" = ">=1.0,<2.0"
416    // The `+` in rez means "this version and above", with optional upper bound after it
417    let s = if s.contains('+')
418        && !s.starts_with('>')
419        && !s.starts_with('<')
420        && !s.starts_with('=')
421        && !s.starts_with('!')
422        && !s.starts_with('~')
423    {
424        // Find the + and split: "1.0+<2.0" -> prefix="1.0", suffix="<2.0"
425        if let Some(plus_pos) = s.find('+') {
426            let prefix = &s[..plus_pos];
427            let suffix = &s[plus_pos + 1..];
428            if suffix.is_empty() {
429                // "1.0+" -> ">=1.0"
430                format!(">={}", prefix)
431            } else {
432                // "1.0+<2.0" -> ">=1.0,<2.0"
433                format!(">={},{}", prefix, suffix)
434            }
435        } else {
436            s.to_string()
437        }
438    } else {
439        s.to_string()
440    };
441
442    let mut bounds = Vec::new();
443
444    // Split on commas first, then spaces that separate constraints
445    let parts = split_constraint_parts(&s);
446
447    for part in parts {
448        let part = part.trim();
449        if part.is_empty() {
450            continue;
451        }
452        let bound = parse_single_constraint(part)?;
453        bounds.push(bound);
454    }
455
456    if bounds.is_empty() {
457        return Ok(BoundSet::any());
458    }
459
460    Ok(BoundSet { bounds })
461}
462
463/// Split a string into individual constraint parts (handles comma and space separators)
464fn split_constraint_parts(s: &str) -> Vec<String> {
465    let mut parts = Vec::new();
466    let mut current = String::new();
467
468    for ch in s.chars() {
469        if ch == ',' {
470            if !current.trim().is_empty() {
471                parts.push(current.trim().to_string());
472            }
473            current = String::new();
474        } else {
475            current.push(ch);
476        }
477    }
478    if !current.trim().is_empty() {
479        parts.push(current.trim().to_string());
480    }
481
482    // Further split space-separated constraints within each part
483    let mut final_parts = Vec::new();
484    for part in parts {
485        let space_parts = split_on_operator_boundaries(&part);
486        final_parts.extend(space_parts);
487    }
488
489    final_parts
490}
491
492/// Split on spaces that are followed by an operator (>=, <=, >, <, ==, !=, ~=)
493fn split_on_operator_boundaries(s: &str) -> Vec<String> {
494    let mut parts = Vec::new();
495    let mut current = String::new();
496
497    let chars: Vec<char> = s.chars().collect();
498    let mut i = 0;
499    while i < chars.len() {
500        let ch = chars[i];
501        if ch == ' ' {
502            // Check if next non-space char starts an operator
503            let mut j = i + 1;
504            while j < chars.len() && chars[j] == ' ' {
505                j += 1;
506            }
507            if j < chars.len() {
508                let next = chars[j];
509                if next == '>' || next == '<' || next == '=' || next == '!' || next == '~' {
510                    if !current.trim().is_empty() {
511                        parts.push(current.trim().to_string());
512                    }
513                    current = String::new();
514                    i = j;
515                    continue;
516                }
517            }
518            current.push(ch);
519        } else {
520            current.push(ch);
521        }
522        i += 1;
523    }
524    if !current.trim().is_empty() {
525        parts.push(current.trim().to_string());
526    }
527    parts
528}
529
530/// Parse a single constraint like `>=1.0`, `<2.0`, `==1.5`, `!=1.0`, `~=1.4`
531fn parse_single_constraint(s: &str) -> Result<Bound, RezCoreError> {
532    let s = s.trim();
533
534    if s.is_empty() || s == "*" {
535        return Ok(Bound::Any);
536    }
537
538    // Try two-char operators first
539    if let Some(rest) = s.strip_prefix(">=") {
540        let v = Version::parse(rest.trim()).map_err(|e| {
541            RezCoreError::VersionRange(format!("Invalid version in range '{}': {}", s, e))
542        })?;
543        return Ok(Bound::Ge(v));
544    }
545    if let Some(rest) = s.strip_prefix("<=") {
546        let v = Version::parse(rest.trim()).map_err(|e| {
547            RezCoreError::VersionRange(format!("Invalid version in range '{}': {}", s, e))
548        })?;
549        return Ok(Bound::Le(v));
550    }
551    if let Some(rest) = s.strip_prefix("==") {
552        let v = Version::parse(rest.trim()).map_err(|e| {
553            RezCoreError::VersionRange(format!("Invalid version in range '{}': {}", s, e))
554        })?;
555        return Ok(Bound::Eq(v));
556    }
557    if let Some(rest) = s.strip_prefix("!=") {
558        let v = Version::parse(rest.trim()).map_err(|e| {
559            RezCoreError::VersionRange(format!("Invalid version in range '{}': {}", s, e))
560        })?;
561        return Ok(Bound::Ne(v));
562    }
563    if let Some(rest) = s.strip_prefix("~=") {
564        let v = Version::parse(rest.trim()).map_err(|e| {
565            RezCoreError::VersionRange(format!("Invalid version in range '{}': {}", s, e))
566        })?;
567        return Ok(Bound::Compatible(v));
568    }
569
570    // Single-char operators
571    if let Some(rest) = s.strip_prefix('>') {
572        let v = Version::parse(rest.trim()).map_err(|e| {
573            RezCoreError::VersionRange(format!("Invalid version in range '{}': {}", s, e))
574        })?;
575        return Ok(Bound::Gt(v));
576    }
577    if let Some(rest) = s.strip_prefix('<') {
578        let v = Version::parse(rest.trim()).map_err(|e| {
579            RezCoreError::VersionRange(format!("Invalid version in range '{}': {}", s, e))
580        })?;
581        return Ok(Bound::Lt(v));
582    }
583
584    // No operator - treat as exact version (rez: bare version = "==version")
585    let v = Version::parse(s).map_err(|e| {
586        RezCoreError::VersionRange(format!("Invalid version constraint '{}': {}", s, e))
587    })?;
588    Ok(Bound::Eq(v))
589}
590
591/// Check if a single BoundSet is satisfiable (not trivially empty due to conflicting bounds)
592fn is_bound_set_satisfiable(bs: &BoundSet) -> bool {
593    // Check for None bounds
594    if bs.bounds.iter().any(|b| matches!(b, Bound::None)) {
595        return false;
596    }
597    // Extract lower and upper bounds to check for contradiction
598    let mut lower: Option<(&Version, bool)> = None; // (version, inclusive)
599    let mut upper: Option<(&Version, bool)> = None; // (version, inclusive)
600
601    for bound in &bs.bounds {
602        match bound {
603            Bound::Any => {}
604            Bound::None => return false,
605            Bound::Ge(v) => match lower {
606                None => lower = Some((v, true)),
607                Some((lv, linc)) => {
608                    if v > lv || (v == lv && !linc) {
609                        lower = Some((v, true));
610                    }
611                }
612            },
613            Bound::Gt(v) => match lower {
614                None => lower = Some((v, false)),
615                Some((lv, _)) => {
616                    if v >= lv {
617                        lower = Some((v, false));
618                    }
619                }
620            },
621            Bound::Le(v) => match upper {
622                None => upper = Some((v, true)),
623                Some((uv, uinc)) => {
624                    if v < uv || (v == uv && !uinc) {
625                        upper = Some((v, true));
626                    }
627                }
628            },
629            Bound::Lt(v) => match upper {
630                None => upper = Some((v, false)),
631                Some((uv, _)) => {
632                    if v <= uv {
633                        upper = Some((v, false));
634                    }
635                }
636            },
637            Bound::Eq(v) => {
638                // Equality constraint acts as both lower and upper bound
639                match lower {
640                    None => lower = Some((v, true)),
641                    Some((lv, linc)) => {
642                        if v > lv || (v == lv && !linc) {
643                            lower = Some((v, true));
644                        } else if v < lv {
645                            // v must be >= lv but eq requires v exactly — contradiction
646                            return false;
647                        }
648                    }
649                }
650                match upper {
651                    None => upper = Some((v, true)),
652                    Some((uv, uinc)) => {
653                        if v < uv || (v == uv && !uinc) {
654                            upper = Some((v, true));
655                        } else if v > uv {
656                            return false;
657                        }
658                    }
659                }
660            }
661            Bound::Ne(_) | Bound::Compatible(_) => {}
662        }
663    }
664
665    // Check if lower and upper bounds are compatible
666    if let (Some((lv, linc)), Some((uv, uinc))) = (lower, upper) {
667        match lv.cmp(uv) {
668            std::cmp::Ordering::Greater => return false,
669            std::cmp::Ordering::Equal => {
670                if !linc || !uinc {
671                    return false;
672                }
673            }
674            std::cmp::Ordering::Less => {}
675        }
676    }
677
678    true
679}
680
681/// Check if two BoundSets can simultaneously be satisfied (have intersection)
682fn bound_sets_intersect(a: &BoundSet, b: &BoundSet) -> bool {
683    // Quick check: if either is Any, they intersect
684    let a_any = a.bounds.iter().all(|bnd| matches!(bnd, Bound::Any));
685    let b_any = b.bounds.iter().all(|bnd| matches!(bnd, Bound::Any));
686    if a_any || b_any {
687        return true;
688    }
689
690    // Combine all bounds and check for structural impossibilities
691    let combined_bounds: Vec<&Bound> = a.bounds.iter().chain(b.bounds.iter()).collect();
692
693    // Check for obvious impossibilities: Eq(v) and Eq(w) where v != w
694    let eq_versions: Vec<&Version> = combined_bounds
695        .iter()
696        .filter_map(|b| if let Bound::Eq(v) = b { Some(v) } else { None })
697        .collect();
698    if eq_versions.len() > 1 {
699        let first = eq_versions[0];
700        if eq_versions.iter().any(|v| *v != first) {
701            return false;
702        }
703    }
704
705    // Compute effective lower and upper bounds from combined set
706    // Lower bound: maximum of all lower bounds (most restrictive)
707    // Upper bound: minimum of all upper bounds (most restrictive)
708    let mut lower: Option<(&Version, bool)> = None; // (version, inclusive)
709    let mut upper: Option<(&Version, bool)> = None; // (version, inclusive)
710
711    for bound in &combined_bounds {
712        match bound {
713            Bound::Ge(v) => match lower {
714                None => lower = Some((v, true)),
715                Some((lv, linc)) => {
716                    if v > lv || (v == lv && !linc) {
717                        lower = Some((v, true));
718                    }
719                }
720            },
721            Bound::Gt(v) => match lower {
722                None => lower = Some((v, false)),
723                Some((lv, _linc)) => {
724                    if v >= lv {
725                        lower = Some((v, false));
726                    }
727                }
728            },
729            Bound::Le(v) => match upper {
730                None => upper = Some((v, true)),
731                Some((uv, uinc)) => {
732                    if v < uv || (v == uv && !uinc) {
733                        upper = Some((v, true));
734                    }
735                }
736            },
737            Bound::Lt(v) => match upper {
738                None => upper = Some((v, false)),
739                Some((uv, _uinc)) => {
740                    if v <= uv {
741                        upper = Some((v, false));
742                    }
743                }
744            },
745            _ => {}
746        }
747    }
748
749    // Check if lower bound and upper bound are compatible
750    if let (Some((lv, linc)), Some((uv, uinc))) = (lower, upper) {
751        match lv.cmp(uv) {
752            std::cmp::Ordering::Greater => return false,
753            std::cmp::Ordering::Equal => {
754                // Equal bounds only feasible if both inclusive
755                if !linc || !uinc {
756                    return false;
757                }
758            }
759            std::cmp::Ordering::Less => {} // feasible
760        }
761    }
762
763    true
764}