use std::fmt::{self, Display, Formatter};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct MatchScore(pub u32);
impl Display for MatchScore {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "success ({})", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct FailureScore(pub u32);
impl Display for FailureScore {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "fail ({})", self.0)
}
}
pub struct TaggedFailureScore<'a> {
pub(crate) score: u32,
pub(crate) why: Box<dyn std::fmt::Display + 'a>,
}
impl TaggedFailureScore<'_> {
pub fn score(&self) -> FailureScore {
FailureScore(self.score)
}
}
impl fmt::Debug for TaggedFailureScore<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("TaggedFailureScore")
.field("score", &self.score)
.field("why", &self.why.to_string())
.finish()
}
}
impl Display for TaggedFailureScore<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.why)
}
}
pub trait DispatchRule<From>: Sized {
type Error: std::fmt::Debug + std::fmt::Display + 'static;
fn try_match(from: &From) -> Result<MatchScore, FailureScore>;
fn convert(from: From) -> Result<Self, Self::Error>;
fn description(f: &mut Formatter<'_>, _from: Option<&From>) -> fmt::Result {
write!(f, "<no description>")
}
fn try_match_verbose<'a>(from: &'a From) -> Result<MatchScore, TaggedFailureScore<'a>>
where
Self: 'a,
{
match Self::try_match(from) {
Ok(score) => Ok(score),
Err(score) => Err(TaggedFailureScore {
score: score.0,
why: Box::new(Why::<From, Self>::new(from)),
}),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Why<'a, From, To> {
from: &'a From,
_to: std::marker::PhantomData<To>,
}
impl<'a, From, To> Why<'a, From, To> {
pub fn new(from: &'a From) -> Self {
Self {
from,
_to: std::marker::PhantomData,
}
}
}
impl<From, To> std::fmt::Display for Why<'_, From, To>
where
To: DispatchRule<From>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
To::description(f, Some(self.from))
}
}
#[derive(Debug, Clone, Copy)]
pub struct Description<From, To> {
_from: std::marker::PhantomData<From>,
_to: std::marker::PhantomData<To>,
}
impl<From, To> Description<From, To> {
pub fn new() -> Self {
Self {
_from: std::marker::PhantomData,
_to: std::marker::PhantomData,
}
}
}
impl<From, To> Default for Description<From, To> {
fn default() -> Self {
Self::new()
}
}
impl<From, To> std::fmt::Display for Description<From, To>
where
To: DispatchRule<From>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
To::description(f, None)
}
}
pub const IMPLICIT_MATCH_SCORE: MatchScore = MatchScore(100000);
impl<T: Sized> DispatchRule<T> for T {
type Error = std::convert::Infallible;
fn try_match(_from: &T) -> Result<MatchScore, FailureScore> {
Ok(IMPLICIT_MATCH_SCORE)
}
fn convert(from: T) -> Result<T, Self::Error> {
Ok(from)
}
fn description(f: &mut Formatter<'_>, from: Option<&T>) -> fmt::Result {
match from {
None => write!(f, "{}", std::any::type_name::<T>()),
Some(_) => write!(f, "identity match"),
}
}
}
impl<'a, T: Sized> DispatchRule<&'a mut T> for &'a T {
type Error = std::convert::Infallible;
fn try_match(_from: &&'a mut T) -> Result<MatchScore, FailureScore> {
Ok(IMPLICIT_MATCH_SCORE)
}
fn convert(from: &'a mut T) -> Result<&'a T, Self::Error> {
Ok(from)
}
fn description(f: &mut Formatter<'_>, from: Option<&&'a mut T>) -> fmt::Result {
match from {
None => write!(f, "&{}", std::any::type_name::<T>()),
Some(_) => write!(f, "identity match"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_match_score() {
let x = MatchScore(10);
let y = MatchScore(20);
assert!(x < y);
assert!(x <= y);
assert!(x <= x);
assert!(x == x);
assert!(x != y);
assert!(y == y);
assert!(y != x);
assert!(y > x);
assert!(y >= x);
assert_eq!(x.to_string(), "success (10)");
}
#[test]
fn test_fail_score() {
let x = FailureScore(10);
let y = FailureScore(20);
assert!(x < y);
assert!(x <= y);
assert!(x <= x);
assert!(x == x);
assert!(x != y);
assert!(y == y);
assert!(y != x);
assert!(y > x);
assert!(y >= x);
assert_eq!(x.to_string(), "fail (10)");
}
#[test]
fn test_tagged_failure() {
let tagged = TaggedFailureScore {
score: 10,
why: Box::new(20),
};
assert_eq!(tagged.score(), FailureScore(10));
assert_eq!(tagged.to_string(), "20");
assert_eq!(
format!("{:?}", tagged),
"TaggedFailureScore { score: 10, why: \"20\" }"
);
}
enum TestEnum {
A,
B,
}
struct TestType;
impl DispatchRule<TestEnum> for TestType {
type Error = std::convert::Infallible;
fn try_match(x: &TestEnum) -> Result<MatchScore, FailureScore> {
match x {
TestEnum::A => Ok(MatchScore(10)),
TestEnum::B => Err(FailureScore(20)),
}
}
fn convert(x: TestEnum) -> Result<Self, Self::Error> {
assert!(matches!(x, TestEnum::A));
Ok(TestType)
}
fn description(f: &mut Formatter<'_>, from: Option<&TestEnum>) -> fmt::Result {
match from {
None => write!(f, "TestEnum::A"),
Some(value) => match value {
TestEnum::A => write!(f, "success"),
TestEnum::B => write!(f, "expected TestEnum::B"),
},
}
}
}
#[test]
fn test_dispatch_helpers() {
let desc = Description::<TestEnum, TestType>::default().to_string();
assert_eq!(desc, "TestEnum::A");
let a = TestEnum::A;
let why = Why::<_, TestType>::new(&a).to_string();
assert_eq!(why, "success");
let b = TestEnum::B;
let why = Why::<_, TestType>::new(&b).to_string();
assert_eq!(why, "expected TestEnum::B");
let result = TestType::try_match_verbose(&a).unwrap();
assert_eq!(result, MatchScore(10));
let result = TestType::try_match_verbose(&b).unwrap_err();
assert_eq!(result.score(), FailureScore(20));
assert_eq!(result.to_string(), "expected TestEnum::B");
TestType::convert(TestEnum::A).unwrap();
}
#[test]
fn test_implicit_conversions() {
let x = f32::try_match(&0.0f32).unwrap();
assert_eq!(x, IMPLICIT_MATCH_SCORE);
let x = f32::convert(0.0f32).unwrap();
assert_eq!(x, 0.0f32);
let x = <&f32>::try_match(&&mut 0.0f32).unwrap();
assert_eq!(x, IMPLICIT_MATCH_SCORE);
let mut x: f32 = 10.0;
let x = <&f32>::convert(&mut x).unwrap();
assert_eq!(*x, 10.0);
assert_eq!(Description::<f32, f32>::new().to_string(), "f32");
assert_eq!(Why::<f32, f32>::new(&0.0f32).to_string(), "identity match");
assert_eq!(Description::<&mut f32, &f32>::new().to_string(), "&f32");
assert_eq!(
Why::<&mut f32, &f32>::new(&&mut 0.0f32).to_string(),
"identity match"
);
}
#[test]
#[should_panic]
fn convert_panics() {
let _ = TestType::convert(TestEnum::B);
}
}