Skip to content

Commit 46d690b

Browse files
committed
Determine exhaustiveness as When instead of bool.
This change uses `BoundaryTerm` to query disjunctive terms and determine not only if a token is or isn't exhaustive but also if it is _sometimes_ exhaustive. `Program::is_exhaustive` now returns `When`, much like `has_root`.
1 parent 6f7072a commit 46d690b

File tree

8 files changed

+149
-145
lines changed

8 files changed

+149
-145
lines changed

src/lib.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ pub trait Program<'t>: Pattern<'t, Error = Infallible> {
201201
/// # Examples
202202
///
203203
/// Text variance can be used to determine if a pattern can be trivially represented by an
204-
/// equivalent path using platform file system APIs.
204+
/// equivalent native path using platform file system APIs.
205205
///
206206
/// ```rust
207207
/// use std::path::Path;
@@ -220,22 +220,24 @@ pub trait Program<'t>: Pattern<'t, Error = Infallible> {
220220
/// A glob expression that begins with a separator `/` has a root, but less trivial patterns
221221
/// like `/**` and `</root:1,>` can also root an expression. Some `Program` types may have
222222
/// indeterminate roots and may match both candidate paths with and without a root. In this
223-
/// case, this functions returns [`Sometimes`] (indeterminate).
223+
/// case, this functions returns [`Sometimes`].
224224
///
225225
/// [`Sometimes`]: crate::query::When::Sometimes
226226
fn has_root(&self) -> When;
227227

228-
/// Returns `true` if the pattern is exhaustive.
228+
/// Describes when the pattern matches candidate paths exhaustively.
229229
///
230230
/// A glob expression is exhaustive if, given a matched candidate path, it necessarily matches
231-
/// any and all sub-trees of that path. For example, glob expressions that end with a tree
232-
/// wildcard like `.local/**` are exhaustive, but so are less trivial expressions like `<<?>/>`
233-
/// and `<doc/<*/:3,>>`. This can be an important property when determining what directory
234-
/// trees to read or which files and directories to select with a pattern.
231+
/// any and all sub-trees of that path. For example, given the pattern `.local/**` and the
232+
/// matched path `.local/bin`, any and all paths beneath `.local/bin` also match the pattern.
235233
///
236-
/// Note that this applies to the **pattern** and **not** to a particular match. It is possible
237-
/// for a particular match against a **non**exhaustive pattern to be exhaustive.
238-
fn is_exhaustive(&self) -> bool;
234+
/// Patterns that end with tree wildcards are more obviously exhaustive, but less trivial
235+
/// patterns like `<<?>/>`, `<doc/<*/:3,>>`, and `{a/**,b/**}` can also be exhaustive. Patterns
236+
/// with alternations may only be exhaustive for some matched paths. In this case, this
237+
/// function returns [`Sometimes`].
238+
///
239+
/// [`Sometimes`]: crate::query::When::Sometimes
240+
fn is_exhaustive(&self) -> When;
239241
}
240242

241243
/// General errors concerning [`Program`]s.
@@ -758,7 +760,7 @@ impl<'t> Program<'t> for Glob<'t> {
758760
self.tree.as_ref().as_token().has_root()
759761
}
760762

761-
fn is_exhaustive(&self) -> bool {
763+
fn is_exhaustive(&self) -> When {
762764
self.tree.as_ref().as_token().is_exhaustive()
763765
}
764766
}
@@ -817,7 +819,7 @@ impl<'t> Program<'t> for Any<'t> {
817819
self.tree.as_ref().as_token().has_root()
818820
}
819821

820-
fn is_exhaustive(&self) -> bool {
822+
fn is_exhaustive(&self) -> When {
821823
self.tree.as_ref().as_token().is_exhaustive()
822824
}
823825
}

src/query.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,14 @@ pub enum Variance<T, B> {
9797
impl<T, B> Variance<T, B> {
9898
pub fn invariant(self) -> Option<T> {
9999
match self {
100-
Variance::Invariant(invariance) => Some(invariance),
100+
Variance::Invariant(invariant) => Some(invariant),
101101
_ => None,
102102
}
103103
}
104104

105105
pub fn variant(self) -> Option<B> {
106106
match self {
107-
Variance::Variant(variance) => Some(variance),
107+
Variance::Variant(bound) => Some(bound),
108108
_ => None,
109109
}
110110
}
@@ -114,8 +114,8 @@ impl<T, B> Variance<T, B> {
114114
/// Produces a new `Variance` that contains a reference `self`, leaving `self` in place.
115115
pub fn as_ref(&self) -> Variance<&T, &B> {
116116
match self {
117-
Variance::Invariant(ref invariance) => Variance::Invariant(invariance),
118-
Variance::Variant(ref variance) => Variance::Variant(variance),
117+
Variance::Invariant(ref invariant) => Variance::Invariant(invariant),
118+
Variance::Variant(ref bound) => Variance::Variant(bound),
119119
}
120120
}
121121

@@ -263,6 +263,22 @@ impl When {
263263
pub fn is_never(&self) -> bool {
264264
matches!(self, When::Never)
265265
}
266+
267+
/// Returns `true` if the truth value is [`Always`] or [`Sometimes`].
268+
///
269+
/// [`Always`]: crate::query::When::Always
270+
/// [`Sometimes`]: crate::query::When::Sometimes
271+
pub fn is_maybe_true(&self) -> bool {
272+
!self.is_never()
273+
}
274+
275+
/// Returns `true` if the truth value is [`Never`] or [`Sometimes`].
276+
///
277+
/// [`Never`]: crate::query::When::Never
278+
/// [`Sometimes`]: crate::query::When::Sometimes
279+
pub fn is_maybe_false(&self) -> bool {
280+
!self.is_always()
281+
}
266282
}
267283

268284
impl From<bool> for When {

src/token/mod.rs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -569,21 +569,16 @@ impl<'t, A> Token<'t, A> {
569569
}
570570
}
571571

572-
// TODO: Exhaustiveness should be expressed with `When` rather than `bool`. In particular,
573-
// alternations may _sometimes_ be exhaustive.
574572
// TODO: There is a distinction between exhaustiveness of a glob and exhaustiveness of a match
575573
// (this is also true of other properties). The latter can be important for performance
576574
// optimization, but may also be useful in the public API (perhaps as part of
577575
// `MatchedText`).
578-
// NOTE: False positives in this function may cause logic errors and are completely
579-
// unacceptable. The discovery of a false positive here almost cetainly indicates a
580-
// serious bug. False positives in negative patterns cause matching to incorrectly
581-
// discard directory trees in the `FileIterator::not` combinator.
582-
pub fn is_exhaustive(&self) -> bool {
576+
// NOTE: False positives in this function cause negated pattern to incorrectly discard
577+
// directory trees in the `FileIterator::not` combinator.
578+
pub fn is_exhaustive(&self) -> When {
583579
self.fold(TreeExhaustiveness)
584-
.map(Finalize::finalize)
585580
.as_ref()
586-
.map_or(false, Variance::is_exhaustive)
581+
.map_or(When::Never, BoundaryTerm::is_exhaustive)
587582
}
588583
}
589584

src/token/variance/invariant/mod.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod text;
33

44
use std::num::NonZeroUsize;
55

6+
use crate::query::When;
67
use crate::token::variance::natural::{
78
define_natural_invariant, BoundedVariantRange, OpenedUpperBound,
89
};
@@ -147,8 +148,17 @@ impl TokenVariance<Depth> {
147148
}
148149

149150
impl BoundaryTerm<Depth> {
150-
pub fn is_exhaustive(&self) -> bool {
151-
self.clone().finalize().is_exhaustive()
151+
pub fn is_exhaustive(&self) -> When {
152+
match self {
153+
BoundaryTerm::Conjunctive(SeparatedTerm(_, ref term)) => term.is_exhaustive().into(),
154+
BoundaryTerm::Disjunctive(ref term) => term
155+
.branches()
156+
.map(AsRef::as_ref)
157+
.map(TokenVariance::<Depth>::is_exhaustive)
158+
.map(When::from)
159+
.reduce(When::certainty)
160+
.unwrap_or(When::Never),
161+
}
152162
}
153163
}
154164

src/token/variance/invariant/term.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ where
142142
{
143143
DisjunctiveTerm(self.0.into_iter().map(f).collect())
144144
}
145+
146+
pub fn branches(&self) -> impl '_ + Iterator<Item = &'_ T> {
147+
self.0.iter()
148+
}
145149
}
146150

147151
impl<T> Conjunction for DisjunctiveTerm<T>
@@ -290,6 +294,18 @@ impl Conjunction for Termination {
290294
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
291295
pub struct SeparatedTerm<T>(pub Termination, pub T);
292296

297+
impl<T> AsMut<T> for SeparatedTerm<T> {
298+
fn as_mut(&mut self) -> &mut T {
299+
&mut self.1
300+
}
301+
}
302+
303+
impl<T> AsRef<T> for SeparatedTerm<T> {
304+
fn as_ref(&self) -> &T {
305+
&self.1
306+
}
307+
}
308+
293309
impl<T, U> Conjunction<SeparatedTerm<U>> for SeparatedTerm<T>
294310
where
295311
SeparatedTerm<T>: Finalize<Output = T>,

0 commit comments

Comments
 (0)