#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum LinearDiscipline {
Linear,
Affine,
Relevant,
Unrestricted,
}
impl LinearDiscipline {
#[must_use]
#[inline]
pub const fn forbids_drop(self) -> bool {
matches!(self, Self::Linear | Self::Relevant)
}
#[must_use]
#[inline]
pub const fn forbids_reuse(self) -> bool {
matches!(self, Self::Linear | Self::Affine)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LinearTypeError {
Dropped {
discipline: LinearDiscipline,
},
Reused {
discipline: LinearDiscipline,
uses: u32,
},
}
pub const fn check_linear_use(
discipline: LinearDiscipline,
uses: u32,
) -> Result<(), LinearTypeError> {
match discipline {
LinearDiscipline::Linear => {
if uses == 0 {
Err(LinearTypeError::Dropped { discipline })
} else if uses > 1 {
Err(LinearTypeError::Reused { discipline, uses })
} else {
Ok(())
}
}
LinearDiscipline::Affine => {
if uses > 1 {
Err(LinearTypeError::Reused { discipline, uses })
} else {
Ok(())
}
}
LinearDiscipline::Relevant => {
if uses == 0 {
Err(LinearTypeError::Dropped { discipline })
} else {
Ok(())
}
}
LinearDiscipline::Unrestricted => Ok(()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn linear_one_use_is_ok() {
assert!(check_linear_use(LinearDiscipline::Linear, 1).is_ok());
}
#[test]
fn linear_zero_uses_is_dropped() {
let err = check_linear_use(LinearDiscipline::Linear, 0).unwrap_err();
assert_eq!(
err,
LinearTypeError::Dropped {
discipline: LinearDiscipline::Linear
}
);
}
#[test]
fn linear_two_uses_is_reused() {
let err = check_linear_use(LinearDiscipline::Linear, 2).unwrap_err();
assert_eq!(
err,
LinearTypeError::Reused {
discipline: LinearDiscipline::Linear,
uses: 2
}
);
}
#[test]
fn affine_zero_or_one_use_is_ok() {
assert!(check_linear_use(LinearDiscipline::Affine, 0).is_ok());
assert!(check_linear_use(LinearDiscipline::Affine, 1).is_ok());
}
#[test]
fn affine_multi_use_is_reused() {
let err = check_linear_use(LinearDiscipline::Affine, 3).unwrap_err();
assert!(matches!(err, LinearTypeError::Reused { uses: 3, .. }));
}
#[test]
fn relevant_zero_uses_is_dropped() {
let err = check_linear_use(LinearDiscipline::Relevant, 0).unwrap_err();
assert!(matches!(err, LinearTypeError::Dropped { .. }));
}
#[test]
fn relevant_any_nonzero_use_is_ok() {
assert!(check_linear_use(LinearDiscipline::Relevant, 1).is_ok());
assert!(check_linear_use(LinearDiscipline::Relevant, 5).is_ok());
assert!(check_linear_use(LinearDiscipline::Relevant, u32::MAX).is_ok());
}
#[test]
fn unrestricted_accepts_anything() {
for uses in [0u32, 1, 2, 100, u32::MAX] {
assert!(check_linear_use(LinearDiscipline::Unrestricted, uses).is_ok());
}
}
#[test]
fn helpers_agree_with_checker_at_boundaries() {
for d in [
LinearDiscipline::Linear,
LinearDiscipline::Affine,
LinearDiscipline::Relevant,
LinearDiscipline::Unrestricted,
] {
assert_eq!(
d.forbids_drop(),
check_linear_use(d, 0).is_err(),
"forbids_drop disagrees with checker at uses=0 for {:?}",
d
);
assert_eq!(
d.forbids_reuse(),
check_linear_use(d, 2).is_err(),
"forbids_reuse disagrees with checker at uses=2 for {:?}",
d
);
}
}
}