#[macro_export]
macro_rules! typeswitch {
($bind:ident as $var:ident { $($rest:tt)* } ) => {{
$crate::typeswitch!(@step $var; $bind $($rest)*)
}};
($modifier:ident $bind:ident as $var:ident { $($rest:tt)* } ) => {{
$crate::typeswitch!(@step $var; $bind $modifier $($rest)*)
}};
($var:ident { $($rest:tt)* } ) => {{
$crate::typeswitch!(@step $var; $($rest)*)
}};
(@step $var:expr; $auto:ident _ => $block:block $($rest:tt)*) => {
$crate::typeswitch!{@step $var; $auto : _ => $block $auto $($rest)*}
};
(@step $var:expr; $auto:ident $modifier:ident _ => $block:block $($rest:tt)*) => {
$crate::typeswitch!{@step $var; $modifier $auto : _ => $block $auto $($rest)*}
};
(@step $var:expr; $auto:ident $modifier:ident $ty:ty => $block:block $($rest:tt)*) => {
$crate::typeswitch!{@step $var; $modifier $auto : $ty => $block $auto $modifier $($rest)*}
};
(@step $var:expr; $auto:ident $ty:ty => $block:block $($rest:tt)*) => {
$crate::typeswitch!{@step $var; $auto : $ty => $block $auto $($rest)*}
};
(@step $var:expr; $modifier:ident $auto:ident : _ => $block:block $($rest:tt)*) => {
$block
};
(@step $var:expr; $auto:ident : _ => $block:block $($rest:tt)*) => {
$block
};
(@step $var:expr; _ => $block:block $($rest:tt)*) => {
$block
};
(@step $var:expr; box $bind:ident : $ty:ty => $block:block $($rest:tt)*) => {
if $var.is::<$ty>() {
let $bind = *$var.downcast::<$ty>().expect("typeswitch: type check passed but downcast failed");
$block
} else {
$crate::typeswitch!{@step $var; $($rest)*}
}
};
(@step $var:expr; mut $bind:ident : $ty:ty => $block:block $($rest:tt)*) => {
if let Some($bind) = <dyn std::any::Any>::downcast_mut::<$ty>(&mut *$var) {
$block
} else {
$crate::typeswitch!{@step $var; $($rest)*}
}
};
(@step $var:expr; $bind:ident : $ty:ty => $block:block $($rest:tt)*) => {
if let Some($bind) = <dyn std::any::Any>::downcast_ref::<$ty>(&*$var) {
$block
} else {
$crate::typeswitch!{@step $var; $($rest)*}
}
};
(@step $var:expr; $ty:ty => $block:block $($rest:tt)*) => {
if <dyn std::any::Any>::is::<$ty>(&*$var as _) {
$block
} else {
$crate::typeswitch!{@step $var; $($rest)*}
}
};
(@step $var:expr; $head:ty | $($tail:ty)|+ => $block:block $($rest:tt)*) => {
if <dyn std::any::Any>::is::<$head>(&$var as _) $(|| <dyn std::any::Any>::is::<$tail>(&* $var))+ {
$block
} else {
$crate::typeswitch!{@step $var; $($rest)*}
}
};
(@step $var:expr;) => {};
(@step $var:expr; $auto:ident) => {};
}
#[cfg(test)]
mod tests {
use std::any::Any;
use super::*;
#[test]
fn test_standard_immutable() {
let x: &dyn Any = &42i32;
let result = typeswitch! { x {
val: i32 => { format!("int {}", val) }
val: String => { format!("string {}", val) }
_ => { "unknown".to_string() }
}
};
assert_eq!(result, "int 42");
}
#[test]
fn test_standard_mutable() {
let mut val = 10i32;
let x: &mut dyn Any = &mut val;
typeswitch! { x {
mut v: i32 => { *v += 10; }
_ => {}
}
}
assert_eq!(val, 20);
}
#[test]
fn test_owned_box() {
let x: Box<dyn Any> = Box::new(String::from("Hello"));
let res = typeswitch! { x {
box s: String => { s }
_ => { String::new() }
}
};
assert_eq!(res, "Hello");
}
#[test]
fn test_pre_binding_syntax() {
let x: &dyn Any = &100i32;
let res = typeswitch! {
v as x {
String => { v.len() }
i32 => { *v as usize }
_ => { 0 }
}
};
assert_eq!(res, 100);
}
#[test]
fn test_pre_binding_mutable_() {
let mut x: Box<dyn Any> = Box::new(String::from("Hello"));
let result = typeswitch! { mut v as x {
i32 => { format!("int {}", v) }
String => { format!("string {}", v) }
_ => { "unknown".to_string() }
}
};
assert_eq!(result, "string Hello");
}
#[test]
fn test_expression_return() {
let x: &dyn Any = &1.5f64;
let val = typeswitch! { x {
f64 => { 1 }
_ => { 0 }
}
};
assert_eq!(val, 1);
}
#[test]
fn test_or_pattern() {
let x: Box<dyn Any> = Box::new(10i64);
let res = typeswitch! { x {
f32 | f64 => { "float" }
i32 | i64 => { "int" }
_ => { "other" }
}
};
assert_eq!(res, "int");
}
#[test]
fn test_type_param() {
fn _func<T: 'static>(t: &T) {
typeswitch! { t {
t: String => {println!("Amen: {t}");}
}}
}
}
}