Skip to content

Commit

Permalink
Merge pull request #202 from YarnSpinnerTool/validate-optional-parame…
Browse files Browse the repository at this point in the history
…ters
  • Loading branch information
janhohenheim authored Jun 27, 2024
2 parents a0ab91c + 9d98acc commit 0f2d488
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 2 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/bevy_plugin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ features = [

[dev-dependencies]
tempfile = "3"
static_assertions = "1.1.0"

[dev-dependencies.bevy]
version = "0.14.0-rc.2"
Expand Down
15 changes: 15 additions & 0 deletions crates/bevy_plugin/src/commands/command_wrapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,5 +334,20 @@ pub mod tests {
accepts_yarn_command(f);
}

macro_rules! assert_is_yarn_command {
(($($param:ty),*) -> $ret:ty) => {
static_assertions::assert_impl_all!(fn($($param),*) -> $ret: YarnCommand<fn($($param),*) -> $ret>);
};
}

macro_rules! assert_is_not_yarn_command {
(($($param:ty),*) -> $ret:ty) => {
static_assertions::assert_not_impl_any!(fn($($param),*) -> $ret: YarnCommand<fn($($param),*) -> $ret>);
};
}

assert_is_yarn_command! { (In<((), Option<()>)>) -> bool }
assert_is_not_yarn_command! { (In<(Option<()>, ())>) -> bool }

fn accepts_yarn_command<Marker>(_: impl YarnCommand<Marker>) {}
}
3 changes: 3 additions & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ yarnspinner_macros = { path = "../macros", version = "0.1" }
prost = "0.12"
serde = { version = "1", features = ["derive"], optional = true }
bevy = { version = "0.14.0-rc.2", default-features = false, optional = true }

[dev-dependencies]
static_assertions = "1.1.0"
1 change: 1 addition & 0 deletions crates/core/src/yarn_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
mod function_registry;
mod function_wrapping;
mod optionality;
mod parameter_wrapping;

pub(crate) use function_registry::*;
Expand Down
33 changes: 33 additions & 0 deletions crates/core/src/yarn_fn/function_wrapping.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::optionality::AllowedOptionalityChain;
use crate::prelude::*;
use std::any::TypeId;
use std::fmt::{Debug, Display, Formatter};
Expand Down Expand Up @@ -179,6 +180,7 @@ macro_rules! impl_yarn_fn_tuple {
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)]
Expand Down Expand Up @@ -362,4 +364,35 @@ mod tests {
{
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 }
}
}
70 changes: 70 additions & 0 deletions crates/core/src/yarn_fn/optionality.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#![allow(missing_debug_implementations)]

use yarnspinner_macros::all_tuples;

/// Marker trait for valid optionality hints.
pub trait Optionality {}

/// An optional parameter or a tuple where
/// the last element is optional.
pub struct Optional;

impl Optionality for Optional {}

/// A parameter that is required.
pub struct Required;

impl Optionality for Required {}

/// A valid chain of optionality hints
/// i.e. a chain where no optional element follows
/// a required element.
pub trait AllowedOptionalityChain {
/// The optionality hint of the last element in the chain.
type Last: Optionality;
}

impl AllowedOptionalityChain for () {
type Last = Required;
}

impl<O: Optionality> AllowedOptionalityChain for (O,) {
type Last = O;
}

impl<O: Optionality> AllowedOptionalityChain for (Required, O) {
type Last = O;
}

impl AllowedOptionalityChain for (Optional, Optional) {
type Last = Optional;
}

// `impl AllowedOptionalityChain for (Optional, Required) {}`
// is intentionally missing (that's the whole point of this trait).

macro_rules! impl_chain {
// Implementations for zero, one and two-element tuples covered manually.
() => {};
($p1:ident) => {};
($p1:ident, $p2:ident) => {};
($($param:ident),*) => {
// A tuple of three or more elements is valid
// if all two-pairs from left to right are valid.
// example: (A, B, C) is valid if (A, B) and (B, C) are.
impl_chain!(@pairwise [$($param),*] [] $($param,)*);
};
(@pairwise [$($param:ident),*] [$($tt:tt)*] $a:ident, $b:ident,) => {
impl_chain!(@emit [$($param),*] [$($tt)* ($a, $b): AllowedOptionalityChain,] $b,);
};
(@pairwise [$($param:ident),*] [$($tt:tt)*] $a:ident, $b:ident, $($tail:ident,)*) => {
impl_chain!(@pairwise [$($param),*] [$($tt)* ($a, $b): AllowedOptionalityChain,] $b, $($tail,)*);
};
(@emit [$($param: ident),*] [$($tt:tt)*] $last:ident,) => {
impl<$($param: Optionality),*> AllowedOptionalityChain for ($($param),*) where $($tt)* {
type Last = $last;
}
};
}

all_tuples!(impl_chain, 0, 16, O);
23 changes: 21 additions & 2 deletions crates/core/src/yarn_fn/parameter_wrapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//!
//! Inspired by <https://promethia-27.github.io/dependency_injection_like_bevy_from_scratch/chapter2/passing_references.html>
use super::optionality::{AllowedOptionalityChain, Optional, Optionality, Required};
use crate::prelude::*;
use std::any::Any;
use std::borrow::Borrow;
Expand Down Expand Up @@ -54,7 +55,13 @@ impl YarnValueWrapper {
pub trait YarnFnParam {
/// The item type returned when constructing this [`YarnFn`] param. The value of this associated type should be `Self`, instantiated with a new lifetime.
/// You could think of `YarnFnParam::Item<'new>` as being an operation that changes the lifetime bound to `Self`.
type Item<'new>: YarnFnParam;
type Item<'new>;

/// Tracks if this parameter is optional or required.
/// This information is used to disallow required parameters to follow optional ones.
/// See the [`AllowedOptionalityChain`] trait for details.
#[doc(hidden)]
type Optionality: Optionality;

#[doc(hidden)]
fn retrieve<'a>(iter: &mut YarnValueWrapperIter<'a>) -> Self::Item<'a>;
Expand All @@ -65,6 +72,7 @@ pub type YarnFnParamItem<'a, P> = <P as YarnFnParam>::Item<'a>;

impl<T: YarnFnParam> YarnFnParam for Option<T> {
type Item<'new> = Option<T::Item<'new>>;
type Optionality = Optional;

fn retrieve<'a>(iter: &mut YarnValueWrapperIter<'a>) -> Self::Item<'a> {
if iter.peek().is_some() {
Expand All @@ -79,8 +87,11 @@ macro_rules! impl_yarn_fn_param_tuple {
($($param: ident),*) => {
#[allow(non_snake_case)]
impl<$($param,)*> YarnFnParam for ($($param,)*)
where $($param: YarnFnParam,)* {
where $($param: YarnFnParam,)*
($(<$param as YarnFnParam>::Optionality,)*): AllowedOptionalityChain
{
type Item<'new> = ($($param::Item<'new>,)*);
type Optionality = <($(<$param as YarnFnParam>::Optionality,)*) as AllowedOptionalityChain>::Last;

#[allow(unused_variables, clippy::unused_unit)] // for n = 0 tuples
fn retrieve<'a>(iter: &mut YarnValueWrapperIter<'a>) -> Self::Item<'a> {
Expand All @@ -107,6 +118,7 @@ where
<T as TryFrom<YarnValue>>::Error: Display,
{
type Item<'new> = ResRef<'new, T>;
type Optionality = Required;

fn retrieve<'a>(iter: &mut YarnValueWrapperIter<'a>) -> Self::Item<'a> {
let value = iter.next().expect("Passed too few arguments to YarnFn");
Expand Down Expand Up @@ -141,6 +153,7 @@ where
U: ?Sized + 'static,
{
type Item<'new> = ResRefBorrow<'new, T, U>;
type Optionality = Required;

fn retrieve<'a>(iter: &mut YarnValueWrapperIter<'a>) -> Self::Item<'a> {
let value = iter.next().expect("Passed too few arguments to YarnFn");
Expand Down Expand Up @@ -168,6 +181,7 @@ where
<T as TryFrom<YarnValue>>::Error: Display,
{
type Item<'new> = ResOwned<T>;
type Optionality = Required;

fn retrieve<'a>(iter: &mut YarnValueWrapperIter<'a>) -> Self::Item<'a> {
let value = iter.next().expect("Passed too few arguments to YarnFn");
Expand All @@ -192,6 +206,7 @@ macro_rules! impl_yarn_fn_param_inner {
($referenced:ty: YarnFnParam) => {
impl YarnFnParam for &$referenced {
type Item<'new> = &'new $referenced;
type Optionality = Required;

fn retrieve<'a>(iter: &mut YarnValueWrapperIter<'a>) -> Self::Item<'a> {
ResRef::<$referenced>::retrieve(iter).value
Expand All @@ -200,6 +215,7 @@ macro_rules! impl_yarn_fn_param_inner {

impl YarnFnParam for $referenced {
type Item<'new> = $referenced;
type Optionality = Required;

fn retrieve<'a>(iter: &mut YarnValueWrapperIter<'a>) -> Self::Item<'a> {
ResOwned::<$referenced>::retrieve(iter).value
Expand All @@ -209,6 +225,7 @@ macro_rules! impl_yarn_fn_param_inner {
($referenced:ty => $owned:ty: YarnFnParam) => {
impl YarnFnParam for &$referenced {
type Item<'new> = &'new $referenced;
type Optionality = Required;

fn retrieve<'a>(iter: &mut YarnValueWrapperIter<'a>) -> Self::Item<'a> {
ResRefBorrow::<$owned, $referenced>::retrieve(iter).value
Expand All @@ -217,6 +234,7 @@ macro_rules! impl_yarn_fn_param_inner {

impl YarnFnParam for &$owned {
type Item<'new> = &'new $owned;
type Optionality = Required;

fn retrieve<'a>(iter: &mut YarnValueWrapperIter<'a>) -> Self::Item<'a> {
ResRef::<$owned>::retrieve(iter).value
Expand All @@ -225,6 +243,7 @@ macro_rules! impl_yarn_fn_param_inner {

impl YarnFnParam for $owned {
type Item<'new> = $owned;
type Optionality = Required;

fn retrieve<'a>(iter: &mut YarnValueWrapperIter<'a>) -> Self::Item<'a> {
ResOwned::<$owned>::retrieve(iter).value
Expand Down

0 comments on commit 0f2d488

Please sign in to comment.