use super::optionality::AllowedOptionalityChain;
use crate::prelude::*;
use std::any::TypeId;
use std::fmt::{Debug, Display, Formatter};
use std::marker::PhantomData;
use yarnspinner_macros::all_tuples;
pub trait YarnFn<Marker>: Clone + Send + Sync {
type Out: IntoYarnValueFromNonYarnValue + 'static;
#[doc(hidden)]
fn call(&self, input: Vec<YarnValue>) -> Self::Out;
fn parameter_types(&self) -> Vec<TypeId>;
fn return_type(&self) -> TypeId {
TypeId::of::<Self::Out>()
}
}
pub trait UntypedYarnFn: Debug + Display + Send + Sync {
#[doc(hidden)]
fn call(&self, input: Vec<YarnValue>) -> YarnValue;
#[doc(hidden)]
fn clone_box(&self) -> Box<dyn UntypedYarnFn>;
fn parameter_types(&self) -> Vec<TypeId>;
fn return_type(&self) -> TypeId;
}
impl Clone for Box<dyn UntypedYarnFn> {
fn clone(&self) -> Self {
self.clone_box()
}
}
impl<Marker, F> UntypedYarnFn for YarnFnWrapper<Marker, F>
where
Marker: 'static,
F: YarnFn<Marker> + 'static + Clone,
F::Out: IntoYarnValueFromNonYarnValue + 'static + Clone,
{
fn call(&self, input: Vec<YarnValue>) -> YarnValue {
let output = self.function.call(input);
output.into_yarn_value()
}
fn clone_box(&self) -> Box<dyn UntypedYarnFn> {
Box::new(self.clone())
}
fn parameter_types(&self) -> Vec<TypeId> {
self.function.parameter_types()
}
fn return_type(&self) -> TypeId {
self.function.return_type()
}
}
pub(crate) struct YarnFnWrapper<Marker, F>
where
F: YarnFn<Marker>,
{
function: F,
_marker: PhantomData<fn() -> Marker>,
}
impl<Marker, F> Clone for YarnFnWrapper<Marker, F>
where
F: YarnFn<Marker>,
{
fn clone(&self) -> Self {
Self {
function: self.function.clone(),
_marker: PhantomData,
}
}
}
impl<Marker, F> From<F> for YarnFnWrapper<Marker, F>
where
F: YarnFn<Marker>,
{
fn from(function: F) -> Self {
Self {
function,
_marker: PhantomData,
}
}
}
impl<Marker, F> Debug for YarnFnWrapper<Marker, F>
where
F: YarnFn<Marker>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let signature = std::any::type_name::<Marker>();
let function_path = std::any::type_name::<F>();
let debug_message = format!("{signature} {{{function_path}}}");
f.debug_struct(&debug_message).finish()
}
}
impl<Marker, F> Display for YarnFnWrapper<Marker, F>
where
F: YarnFn<Marker>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let signature = std::any::type_name::<Marker>();
f.write_str(signature)
}
}
impl PartialEq for Box<dyn UntypedYarnFn> {
fn eq(&self, other: &Self) -> bool {
let debug = format!("{:?}", self);
let other_debug = format!("{:?}", other);
debug == other_debug
}
}
impl Eq for Box<dyn UntypedYarnFn> {}
#[macro_export]
macro_rules! yarn_fn_type {
(impl Fn($($param:ty),+) -> $ret:ty) => {
impl $crate::prelude::YarnFn<fn($($param),+) -> $ret, Out = $ret>
};
}
pub use yarn_fn_type;
macro_rules! impl_yarn_fn_tuple {
($($param: ident),*) => {
#[allow(non_snake_case)]
impl<F, O, $($param,)*> YarnFn<fn($($param,)*) -> O> for F
where
for<'a> F:
Send + Sync + Clone +
Fn($($param,)*) -> O +
Fn($(<$param as YarnFnParam>::Item<'a>,)*) -> O,
O: IntoYarnValueFromNonYarnValue + 'static,
$($param: YarnFnParam + 'static,)*
($(<$param as YarnFnParam>::Optionality,)*): AllowedOptionalityChain,
{
type Out = O;
#[allow(non_snake_case)]
fn call(&self, input: Vec<YarnValue>) -> Self::Out {
let mut params: Vec<_> = input.into_iter().map(YarnValueWrapper::from).collect();
#[allow(unused_variables, unused_mut)] let mut iter = params.iter_mut().peekable();
let input = (
$($param::retrieve(&mut iter),)*
);
assert!(iter.next().is_none(), "Passed too many arguments to YarnFn");
let ($($param,)*) = input;
self($($param,)*)
}
fn parameter_types(&self) -> Vec<TypeId> {
vec![$(TypeId::of::<$param>()),*]
}
}
};
}
all_tuples!(impl_yarn_fn_tuple, 0, 16, P);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_no_params() {
fn f() -> bool {
true
}
accept_yarn_fn(f);
}
#[test]
fn accepts_string() {
fn f(_: String) -> bool {
true
}
accept_yarn_fn(f);
}
#[test]
fn accepts_string_ref() {
fn f(_: &String) -> bool {
true
}
accept_yarn_fn(f);
}
#[test]
fn accepts_string_slice() {
fn f(_: &str) -> bool {
true
}
accept_yarn_fn(f);
}
#[test]
fn accepts_usize() {
fn f(_: usize) -> bool {
true
}
accept_yarn_fn(f);
}
#[test]
fn accepts_usize_ref() {
fn f(_: &usize) -> bool {
true
}
accept_yarn_fn(f);
}
#[test]
fn accepts_yarn_value() {
fn f(_: YarnValue) -> bool {
true
}
accept_yarn_fn(f);
}
#[test]
fn accepts_yarn_value_ref() {
fn f(_: &YarnValue) -> bool {
true
}
accept_yarn_fn(f);
}
#[test]
fn accepts_optional_value() {
fn f(_: Option<String>) -> bool {
true
}
accept_yarn_fn(f);
}
#[test]
fn accepts_optional_value_ref() {
fn f(_: Option<&YarnValue>) -> bool {
true
}
accept_yarn_fn(f);
}
#[test]
fn accepts_multiple_strings() {
fn f(s: String, _: String, _: &str, _: String, _: &str) -> String {
s
}
accept_yarn_fn(f);
}
#[test]
fn accepts_lots_of_different_types() {
#[allow(clippy::too_many_arguments)]
fn f(
_: String,
_: usize,
_: &str,
_: &YarnValue,
_: &bool,
_: isize,
_: String,
_: &u32,
) -> bool {
true
}
accept_yarn_fn(f);
}
#[test]
fn accepts_tuples() {
#[allow(clippy::too_many_arguments)]
fn f(
_: (String, usize),
_: usize,
_: (&str, (&str, &String)),
_: &YarnValue,
_: (&bool, bool, bool, (&str, String)),
_: isize,
_: String,
_: &u32,
) -> bool {
true
}
accept_yarn_fn(f);
}
#[test]
fn unpacks_tuples_in_right_order() {
#[allow(clippy::too_many_arguments)]
fn f(a: usize, (b, c): (usize, usize), d: usize, (e, f, g): (usize, usize, usize)) -> bool {
a == 1 && b == 2 && c == 3 && d == 4 && e == 5 && f == 6 && g == 7
}
let input: Vec<_> = (1..=7).map(YarnValue::from).collect();
let result = apply_yarn_fn(f, input);
assert!(result);
}
#[test]
fn accepts_function_with_single_tuple_param() {
fn f(_: (usize, isize, (String, &str))) -> bool {
true
}
accept_yarn_fn(f);
}
fn accept_yarn_fn<Marker>(_: impl YarnFn<Marker>) {}
fn apply_yarn_fn<T, Marker>(f: T, input: Vec<YarnValue>) -> T::Out
where
T: YarnFn<Marker>,
{
f.call(input)
}
mod optionality {
use super::*;
macro_rules! assert_is_yarn_fn {
(($($param:ty),*) -> $ret:ty) => {
static_assertions::assert_impl_all!(fn($($param),*) -> $ret: YarnFn<fn($($param),*) -> $ret>);
};
}
macro_rules! assert_is_not_yarn_fn {
(($($param:ty),*) -> $ret:ty) => {
static_assertions::assert_not_impl_any!(fn($($param),*) -> $ret: YarnFn<fn($($param),*) -> $ret>);
};
}
assert_is_yarn_fn! { (()) -> bool }
assert_is_yarn_fn! { (Option<()>) -> bool }
assert_is_yarn_fn! { ((), ()) -> bool }
assert_is_yarn_fn! { ((), Option<()>) -> bool }
assert_is_yarn_fn! { (Option<()>, Option<()>) -> bool }
assert_is_not_yarn_fn! { (Option<()>, ()) -> bool }
assert_is_yarn_fn! { (Option<()>, Option<()>, Option<()>, Option<()>) -> bool }
assert_is_not_yarn_fn! { (Option<()>, Option<()>, Option<()>, ()) -> bool }
assert_is_yarn_fn! { (((), (), ()), ((), Option<()>), (Option<()>, Option<()>)) -> bool }
assert_is_yarn_fn! { ((), ((), ((), ((), Option<()>)))) -> bool }
assert_is_not_yarn_fn! { ((), ((), ((), ((), Option<()>))), ()) -> bool }
}
}