Skip to content

Commit

Permalink
Determine exhaustiveness as When instead of bool.
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
olson-sean-k committed Mar 26, 2024
1 parent 6f7072a commit f984ec9
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 139 deletions.
26 changes: 14 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ pub trait Program<'t>: Pattern<'t, Error = Infallible> {
/// # Examples
///
/// Text variance can be used to determine if a pattern can be trivially represented by an
/// equivalent path using platform file system APIs.
/// equivalent native path using platform file system APIs.
///
/// ```rust
/// use std::path::Path;
Expand All @@ -220,22 +220,24 @@ pub trait Program<'t>: Pattern<'t, Error = Infallible> {
/// A glob expression that begins with a separator `/` has a root, but less trivial patterns
/// like `/**` and `</root:1,>` can also root an expression. Some `Program` types may have
/// indeterminate roots and may match both candidate paths with and without a root. In this
/// case, this functions returns [`Sometimes`] (indeterminate).
/// case, this functions returns [`Sometimes`].
///
/// [`Sometimes`]: crate::query::When::Sometimes
fn has_root(&self) -> When;

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

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

fn is_exhaustive(&self) -> bool {
fn is_exhaustive(&self) -> When {
self.tree.as_ref().as_token().is_exhaustive()
}
}
Expand Down Expand Up @@ -817,7 +819,7 @@ impl<'t> Program<'t> for Any<'t> {
self.tree.as_ref().as_token().has_root()
}

fn is_exhaustive(&self) -> bool {
fn is_exhaustive(&self) -> When {
self.tree.as_ref().as_token().is_exhaustive()
}
}
Expand Down
24 changes: 20 additions & 4 deletions src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,14 @@ pub enum Variance<T, B> {
impl<T, B> Variance<T, B> {
pub fn invariant(self) -> Option<T> {
match self {
Variance::Invariant(invariance) => Some(invariance),
Variance::Invariant(invariant) => Some(invariant),
_ => None,
}
}

pub fn variant(self) -> Option<B> {
match self {
Variance::Variant(variance) => Some(variance),
Variance::Variant(bound) => Some(bound),
_ => None,
}
}
Expand All @@ -114,8 +114,8 @@ impl<T, B> Variance<T, B> {
/// Produces a new `Variance` that contains a reference `self`, leaving `self` in place.
pub fn as_ref(&self) -> Variance<&T, &B> {
match self {
Variance::Invariant(ref invariance) => Variance::Invariant(invariance),
Variance::Variant(ref variance) => Variance::Variant(variance),
Variance::Invariant(ref invariant) => Variance::Invariant(invariant),
Variance::Variant(ref bound) => Variance::Variant(bound),
}
}

Expand Down Expand Up @@ -263,6 +263,22 @@ impl When {
pub fn is_never(&self) -> bool {
matches!(self, When::Never)
}

/// Returns `true` if the truth value is [`Always`] or [`Sometimes`].
///
/// [`Always`]: crate::query::When::Always
/// [`Sometimes`]: crate::query::When::Sometimes
pub fn is_maybe_true(&self) -> bool {
!self.is_never()
}

/// Returns `true` if the truth value is [`Never`] or [`Sometimes`].
///
/// [`Never`]: crate::query::When::Never
/// [`Sometimes`]: crate::query::When::Sometimes
pub fn is_maybe_false(&self) -> bool {
!self.is_always()
}
}

impl From<bool> for When {
Expand Down
9 changes: 6 additions & 3 deletions src/token/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -579,11 +579,14 @@ impl<'t, A> Token<'t, A> {
// unacceptable. The discovery of a false positive here almost cetainly indicates a
// serious bug. False positives in negative patterns cause matching to incorrectly
// discard directory trees in the `FileIterator::not` combinator.
pub fn is_exhaustive(&self) -> bool {
pub fn is_exhaustive(&self) -> When {
//self.fold(TreeExhaustiveness)
// .map(Finalize::finalize)
// .as_ref()
// .map_or(false, Variance::is_exhaustive)
self.fold(TreeExhaustiveness)
.map(Finalize::finalize)
.as_ref()
.map_or(false, Variance::is_exhaustive)
.map_or(When::Never, BoundaryTerm::is_exhaustive)
}
}

Expand Down
14 changes: 12 additions & 2 deletions src/token/variance/invariant/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod text;

use std::num::NonZeroUsize;

use crate::query::When;
use crate::token::variance::natural::{
define_natural_invariant, BoundedVariantRange, OpenedUpperBound,
};
Expand Down Expand Up @@ -147,8 +148,17 @@ impl TokenVariance<Depth> {
}

impl BoundaryTerm<Depth> {
pub fn is_exhaustive(&self) -> bool {
self.clone().finalize().is_exhaustive()
pub fn is_exhaustive(&self) -> When {
match self {
BoundaryTerm::Conjunctive(SeparatedTerm(_, ref term)) => term.is_exhaustive().into(),
BoundaryTerm::Disjunctive(ref term) => term
.branches()
.map(AsRef::as_ref)
.map(TokenVariance::<Depth>::is_exhaustive)
.map(When::from)
.reduce(When::certainty)
.unwrap_or(When::Never),
}
}
}

Expand Down
16 changes: 16 additions & 0 deletions src/token/variance/invariant/term.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ where
{
DisjunctiveTerm(self.0.into_iter().map(f).collect())
}

pub fn branches(&self) -> impl '_ + Iterator<Item = &'_ T> {
self.0.iter()
}
}

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

impl<T> AsMut<T> for SeparatedTerm<T> {
fn as_mut(&mut self) -> &mut T {
&mut self.1
}
}

impl<T> AsRef<T> for SeparatedTerm<T> {
fn as_ref(&self) -> &T {
&self.1
}
}

impl<T, U> Conjunction<SeparatedTerm<U>> for SeparatedTerm<T>
where
SeparatedTerm<T>: Finalize<Output = T>,
Expand Down
Loading

0 comments on commit f984ec9

Please sign in to comment.