Skip to content

Commit

Permalink
chore!: Panic upon creation of inconsistent view
Browse files Browse the repository at this point in the history
Closes #51
  • Loading branch information
alexpovel committed Nov 1, 2023
1 parent 59d4221 commit ad6a38a
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 0 deletions.
83 changes: 83 additions & 0 deletions src/scoping/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,41 @@ impl<'viewee> ROScopes<'viewee> {
}
}

/// Checks for equality, regarding only raw [`str`] parts, i.e. disregards whether an
/// element is [`In`] or [`Out`] of scope.
impl PartialEq<&str> for ROScopes<'_> {
fn eq(&self, other: &&str) -> bool {
let mut start = 0;
let mut end = None;

for scope in &self.0 {
let s: &str = scope.into();
end = Some(start + s.len());

let Some(substring) = other.get(start..end.unwrap()) else {
return false;
};

if substring != s {
return false;
}

start = end.unwrap();
}

match end {
Some(e) => other.len() == e,
None => other.is_empty(),
}
}
}

impl PartialEq<ROScopes<'_>> for &str {
fn eq(&self, other: &ROScopes<'_>) -> bool {
other == self
}
}

impl<'viewee> From<&'viewee ROScope<'viewee>> for &'viewee str {
/// Get the underlying string slice.
///
Expand Down Expand Up @@ -120,3 +155,51 @@ impl<'viewee> From<&'viewee RWScope<'viewee>> for &'viewee str {
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;

#[rstest]
// Base cases
#[case(ROScopes(vec![ROScope(In("abc"))]), "abc", true)]
#[case(ROScopes(vec![ROScope(In("cba"))]), "cba", true)]
#[case(ROScopes(vec![ROScope(In("🦀"))]), "🦀", true)]
#[case(ROScopes(vec![ROScope(In("🦀"))]), "🤗", false)]
//
// Substring matching
#[case(ROScopes(vec![ROScope(In("a")), ROScope(In("b"))]), "ab", true)]
#[case(ROScopes(vec![ROScope(In("a")), ROScope(In("b")), ROScope(In("c"))]), "abc", true)]
//
#[case(ROScopes(vec![ROScope(In("a")), ROScope(In("b"))]), "ac", false)]
#[case(ROScopes(vec![ROScope(In("a")), ROScope(In("b"))]), "a", false)]
#[case(ROScopes(vec![ROScope(In("a")), ROScope(In("b"))]), "b", false)]
#[case(ROScopes(vec![ROScope(In("a")), ROScope(In("b")), ROScope(In("c"))]), "acc", false)]
//
// Length mismatch
#[case(ROScopes(vec![ROScope(In("abc"))]), "abcd", false)]
#[case(ROScopes(vec![ROScope(In("abcd"))]), "abc", false)]
//
// Partial emptiness
#[case(ROScopes(vec![ROScope(In("abc"))]), "", false)]
#[case(ROScopes(vec![ROScope(In(""))]), "abc", false)]
#[case(ROScopes(vec![ROScope(Out(""))]), "abc", false)]
#[case(ROScopes(vec![ROScope(In("")), ROScope(Out(""))]), "abc", false)]
//
// Full emptiness
#[case(ROScopes(vec![ROScope(In(""))]), "", true)]
#[case(ROScopes(vec![ROScope(Out(""))]), "", true)]
#[case(ROScopes(vec![ROScope(In("")), ROScope(Out(""))]), "", true)]
//
// Types of scope doesn't matter
#[case(ROScopes(vec![ROScope(In("a"))]), "a", true)]
#[case(ROScopes(vec![ROScope(Out("a"))]), "a", true)]
fn test_scoped_view_str_equality(
#[case] scopes: ROScopes<'_>,
#[case] string: &str,
#[case] equal: bool,
) {
assert!((scopes == string) == equal);
}
}
22 changes: 22 additions & 0 deletions src/scoping/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ impl fmt::Display for ScopedView<'_> {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScopedViewBuilder<'viewee> {
scopes: ROScopes<'viewee>,
viewee: &'viewee str,
}

/// Core implementations.
Expand All @@ -193,6 +194,7 @@ impl<'viewee> ScopedViewBuilder<'viewee> {
pub fn new(input: &'viewee str) -> Self {
Self {
scopes: ROScopes(vec![ROScope(In(input))]),
viewee: input,
}
}

Expand Down Expand Up @@ -244,6 +246,12 @@ impl<'viewee> ScopedViewBuilder<'viewee> {
/// - or partially [`In`] scope, partially [`Out`] of scope
///
/// after application. Anything [`Out`] out of scope can never be brought back.
///
/// ## Panics
///
/// Panics if the [`Scoper`] scopes such that the view is no longer consistent, i.e.
/// gaps were created and the original input can no longer be reconstructed from the
/// new view.
pub fn explode(&mut self, scoper: &impl Scoper) -> &mut Self {
trace!("Exploding scopes: {:?}", self.scopes);
let mut new = Vec::with_capacity(self.scopes.0.len());
Expand Down Expand Up @@ -272,6 +280,20 @@ impl<'viewee> ScopedViewBuilder<'viewee> {
trace!("Done exploding scopes.");

self.scopes.0 = new;

assert_eq!(
// Tried to do this 'more proper' using the `contracts` crate, but this
// method `mut`ably borrows `self` and returns it as such, which is
// worst-case and didn't play well with its macros. The crate doesn't do
// much more than this manual `assert` anyway.
self.scopes,
self.viewee,
"Post-condition violated: exploding scopes resulted in inconsistent view. \
Aborting, as this is an unrecoverable bug in a scoper. \
Please report at {}.",
env!("CARGO_PKG_REPOSITORY")
);

self
}
}
Expand Down

0 comments on commit ad6a38a

Please sign in to comment.