2
0
Fork 0
mirror of https://github.com/bevyengine/bevy synced 2024-12-24 12:03:14 +00:00

bevy_reflect: Function Overloading (Generic & Variadic Functions) ()

# Objective

Currently function reflection requires users to manually monomorphize
their generic functions. For example:

```rust
fn add<T: Add<Output=T>>(a: T, b: T) -> T {
    a + b
}

// We have to specify the type of `T`:
let reflect_add = add::<i32>.into_function();
```

This PR doesn't aim to solve that problem—this is just a limitation in
Rust. However, it also means that reflected functions can only ever work
for a single monomorphization. If we wanted to support other types for
`T`, we'd have to create a separate function for each one:

```rust
let reflect_add_i32 = add::<i32>.into_function();
let reflect_add_u32 = add::<u32>.into_function();
let reflect_add_f32 = add::<f32>.into_function();
// ...
```

So in addition to requiring manual monomorphization, we also lose the
benefit of having a single function handle multiple argument types.

If a user wanted to create a small modding script that utilized function
reflection, they'd have to either:
- Store all sets of supported monomorphizations and require users to
call the correct one
- Write out some logic to find the correct function based on the given
arguments

While the first option would work, it wouldn't be very ergonomic. The
second option is better, but it adds additional complexity to the user's
logic—complexity that `bevy_reflect` could instead take on.

## Solution

Introduce [function
overloading](https://en.wikipedia.org/wiki/Function_overloading).

A `DynamicFunction` can now be overloaded with other `DynamicFunction`s.
We can rewrite the above code like so:

```rust
let reflect_add = add::<i32>
    .into_function()
    .with_overload(add::<u32>)
    .with_overload(add::<f32>);
```

When invoked, the `DynamicFunction` will attempt to find a matching
overload for the given set of arguments.

And while I went into this PR only looking to improve generic function
reflection, I accidentally added support for variadic functions as well
(hence why I use the broader term "overload" over "generic").

```rust
// Supports 1 to 4 arguments
let multiply_all = (|a: i32| a)
    .into_function()
    .with_overload(|a: i32, b: i32| a * b)
    .with_overload(|a: i32, b: i32, c: i32| a * b * c)
    .with_overload(|a: i32, b: i32, c: i32, d: i32| a * b * c * d);
```

This is simply an added bonus to this particular implementation. ~~Full
variadic support (i.e. allowing for an indefinite number of arguments)
will be added in a later PR.~~ I actually decided to limit the maximum
number of arguments to 63 to supplement faster lookups, a reduced memory
footprint, and faster cloning.

### Alternatives & Rationale

I explored a few options for handling generic functions. This PR is the
one I feel the most confident in, but I feel I should mention the others
and why I ultimately didn't move forward with them.

#### Adding `GenericDynamicFunction`

**TL;DR:** Adding a distinct `GenericDynamicFunction` type unnecessarily
splits and complicates the API.

<details>
<summary>Details</summary>

My initial explorations involved a dedicated `GenericDynamicFunction` to
contain and handle the mappings.

This was initially started back when `DynamicFunction` was distinct from
`DynamicClosure`. My goal was to not prevent us from being able to
somehow make `DynamicFunction` implement `Copy`. But once we reverted
back to a single `DynamicFunction`, that became a non-issue.

But that aside, the real problem was that it created a split in the API.
If I'm using a third-party library that uses function reflection, I have
to know whether to request a `DynamicFunction` or a
`GenericDynamicFunction`. I might not even know ahead of time which one
I want. It might need to be determined at runtime.

And if I'm creating a library, I might want a type to contain both
`DynamicFunction` and `GenericDynamicFunction`. This might not be
possible if, for example, I need to store the function in a `HashMap`.

The other concern is with `IntoFunction`. Right now `DynamicFunction`
trivially implements `IntoFunction` since it can just return itself. But
what should `GenericDynamicFunction` do? It could return itself wrapped
into a `DynamicFunction`, but then the API for `DynamicFunction` would
have to account for this. So then what was the point of having a
separate `GenericDynamicFunction` anyways?

And even apart from `IntoFunction`, there's nothing stopping someone
from manually creating a generic `DynamicFunction` through lying about
its `FunctionInfo` and wrapping a `GenericDynamicFunction`.

That being said, this is probably the "best" alternative if we added a
`Function` trait and stored functions as `Box<dyn Function>`.

However, I'm not convinced we gain much from this. Sure, we could keep
the API for `DynamicFunction` the same, but consumers of `Function` will
need to account for `GenericDynamicFunction` regardless (e.g. handling
multiple `FunctionInfo`, a ranged argument count, etc.). And for all
cases, except where using `DynamicFunction` directly, you end up
treating them all like `GenericDynamicFunction`.

Right now, if we did go with `GenericDynamicFunction`, the only major
benefit we'd gain would be saving 24 bytes. If memory ever does become
an issue here, we could swap over. But I think for the time being it's
better for us to pursue a clearer mental model and end-user ergonomics
through unification.

</details>

##### Using the `FunctionRegistry`

**TL;DR:** Having overloads only exist in the `FunctionRegistry`
unnecessarily splits and complicates the API.

<details>
<summary>Details</summary>

Another idea was to store the overloads in the `FunctionRegistry`. Users
would then just call functions directly through the registry (i.e.
`registry.call("my_func", my_args)`).

I didn't go with this option because of how it specifically relies on
the functions being registered. You'd not only always need access to the
registry, but you'd need to ensure that the functions you want to call
are even registered.

It also means you can't just store a generic `DynamicFunction` on a
type. Instead, you'll need to store the function's name and use that to
look up the function in the registry—even if it's only ever used by that
type.

Doing so also removes all the benefits of `DynamicFunction`, such as the
ability to pass it to functions accepting `IntoFunction`, modify it if
needed, and so on.

Like `GenericDynamicFunction` this introduces a split in the ecosystem:
you either store `DynamicFunction`, store a string to look up the
function, or force `DynamicFunction` to wrap your generic function
anyways. Or worse yet: have `DynamicFunction` wrap the lookup function
using `FunctionRegistryArc`.

</details>

#### Generic `ArgInfo`

**TL;DR:** Allowing `ArgInfo` and `ReturnInfo` to store the generic
information introduces a footgun when interpreting `FunctionInfo`.

<details>
<summary>Details</summary>

Regardless of how we represent a generic function, one thing is clear:
we need to be able to represent the information for such a function.

This PR does so by introducing a `FunctionInfoType` enum to wrap one or
more `FunctionInfo` values.

Originally, I didn't do this. I had `ArgInfo` and `ReturnInfo` allow for
generic types. This allowed us to have a single `FunctionInfo` to
represent our function, but then I realized that it actually lies about
our function.

If we have two `ArgInfo` that both allow for either `i32` or `u32`, what
does this tell us about our function? It turns out: nothing! We can't
know whether our function takes `(i32, i32)`, `(u32, u32)`, `(i32,
u32)`, or `(u32, i32)`.

It therefore makes more sense to just represent a function with multiple
`FunctionInfo` since that's really what it's made up of.

</details>

#### Flatten `FunctionInfo`

**TL;DR:** Flattening removes additional per-overload information some
users may desire and prevents us from adding more information in the
future.

<details>
<summary>Details</summary>

Why don't we just flatten multiple `FunctionInfo` into just one that can
contain multiple signatures?

This is something we could do, but I decided against it for a few
reasons:
- The only thing we'd be able to get rid of for each signature would be
the `name`. While not enough to not do it, it doesn't really suggest we
*have* to either.
- Some consumers may want access to the names of the functions that make
up the overloaded function. For example, to track a bug where an
undesirable function is being added as an overload. Or to more easily
locate the original function of an overload.
- We may eventually allow for more information to be stored on
`FunctionInfo`. For example, we may allow for documentation to be stored
like we do for `TypeInfo`. Consumers of this documentation may want
access to the documentation of each overload as they may provide
documentation specific to that overload.

</details>

## Testing

This PR adds lots of tests and benchmarks, and also adds to the example.

To run the tests:

```
cargo test --package bevy_reflect --all-features
```

To run the benchmarks:

```
cargo bench --bench reflect_function --all-features
```

To run the example:

```
cargo run --package bevy --example function_reflection --all-features
```

### Benchmarks

One of my goals with this PR was to leave the typical case of
non-overloaded functions largely unaffected by the changes introduced in
this PR. ~~And while the static size of `DynamicFunction` has increased
by 17% (from 136 to 160 bytes), the performance has generally stayed the
same~~ The static size of `DynamicFunction` has decreased from 136 to
112 bytes, while calling performance has generally stayed the same:

|                                     | `main` | 7d293ab | 252f3897d |
|-------------------------------------|--------|---------|-----------|
| `into/function`                     | 37 ns  | 46 ns   | 142 ns    |
| `with_overload/01_simple_overload`  | -      | 149 ns  | 268 ns    |
| `with_overload/01_complex_overload` | -      | 332 ns  | 431 ns    |
| `with_overload/10_simple_overload`  | -      | 1266 ns | 2618 ns   |
| `with_overload/10_complex_overload` | -      | 2544 ns | 4170 ns   |
| `call/function`                     | 57 ns  | 58 ns   | 61 ns     |
| `call/01_simple_overload`           | -      | 255 ns  | 242 ns    |
| `call/01_complex_overload`          | -      | 595 ns  | 431 ns    |
| `call/10_simple_overload`           | -      | 740 ns  | 699 ns    |
| `call/10_complex_overload`          | -      | 1824 ns | 1618 ns   |

For the overloaded function tests, the leading number indicates how many
overloads there are: `01` indicates 1 overload, `10` indicates 10
overloads. The `complex` cases have 10 unique generic types and 10
arguments, compared to the `simple` 1 generic type and 2 arguments.

I aimed to prioritize the performance of calling the functions over
creating them, hence creation speed tends to be a bit slower.

There may be other optimizations we can look into but that's probably
best saved for a future PR.

The important bit is that the standard ~~`into/function`~~ and
`call/function` benchmarks show minimal regressions. Since the latest
changes, `into/function` does have some regressions, but again the
priority was `call/function`. We can probably optimize `into/function`
if needed in the future.

---

## Showcase

Function reflection now supports [function
overloading](https://en.wikipedia.org/wiki/Function_overloading)! This
can be used to simulate generic functions:

```rust
fn add<T: Add<Output=T>>(a: T, b: T) -> T {
    a + b
}

let reflect_add = add::<i32>
    .into_function()
    .with_overload(add::<u32>)
    .with_overload(add::<f32>);

let args = ArgList::default().push_owned(25_i32).push_owned(75_i32);  
let result = func.call(args).unwrap().unwrap_owned();  
assert_eq!(result.try_take::<i32>().unwrap(), 100);  
  
let args = ArgList::default().push_owned(25.0_f32).push_owned(75.0_f32);  
let result = func.call(args).unwrap().unwrap_owned();  
assert_eq!(result.try_take::<f32>().unwrap(), 100.0);
```

You can also simulate variadic functions:

```rust
#[derive(Reflect, PartialEq, Debug)]
struct Player {
    name: Option<String>,
    health: u32,
}

// Creates a `Player` with one of the following:  
// - No name and 100 health  
// - A name and 100 health  
// - No name and custom health  
// - A name and custom health
let create_player = (|| Player {
        name: None,
        health: 100,
    })
    .into_function()
    .with_overload(|name: String| Player {
        name: Some(name),
        health: 100,
    })
    .with_overload(|health: u32| Player {
        name: None,
        health
    })
    .with_overload(|name: String, health: u32| Player {
        name: Some(name),
        health,
    });

let args = ArgList::default()
    .push_owned(String::from("Urist"))
    .push_owned(55_u32);
    
let player = create_player
    .call(args)
    .unwrap()
    .unwrap_owned()
    .try_take::<Player>()
    .unwrap();
	
assert_eq!(
    player,
    Player {
        name: Some(String::from("Urist")),
        health: 55
    }
);
```
This commit is contained in:
Gino Valente 2024-12-09 18:51:47 -07:00 committed by GitHub
parent 1e7b89cdf5
commit d21c7a1911
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 2583 additions and 252 deletions

View file

@ -1,7 +1,7 @@
use bevy_reflect::func::{ArgList, IntoFunction, IntoFunctionMut, TypedFunction}; use bevy_reflect::func::{ArgList, IntoFunction, IntoFunctionMut, TypedFunction};
use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
criterion_group!(benches, typed, into, call, clone); criterion_group!(benches, typed, into, call, overload, clone);
criterion_main!(benches); criterion_main!(benches);
fn add(a: i32, b: i32) -> i32 { fn add(a: i32, b: i32) -> i32 {
@ -79,6 +79,307 @@ fn call(c: &mut Criterion) {
}); });
} }
fn overload(c: &mut Criterion) {
fn add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T {
a + b
}
fn complex<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>(
_: T0,
_: T1,
_: T2,
_: T3,
_: T4,
_: T5,
_: T6,
_: T7,
_: T8,
_: T9,
) {
}
c.benchmark_group("with_overload")
.bench_function("01_simple_overload", |b| {
b.iter_batched(
|| add::<i8>.into_function(),
|func| func.with_overload(add::<i16>),
BatchSize::SmallInput,
);
})
.bench_function("01_complex_overload", |b| {
b.iter_batched(
|| complex::<i8, i16, i32, i64, i128, u8, u16, u32, u64, u128>.into_function(),
|func| {
func.with_overload(complex::<i16, i32, i64, i128, u8, u16, u32, u64, u128, i8>)
},
BatchSize::SmallInput,
);
})
.bench_function("03_simple_overload", |b| {
b.iter_batched(
|| add::<i8>.into_function(),
|func| {
func.with_overload(add::<i16>)
.with_overload(add::<i32>)
.with_overload(add::<i64>)
},
BatchSize::SmallInput,
);
})
.bench_function("03_complex_overload", |b| {
b.iter_batched(
|| complex::<i8, i16, i32, i64, i128, u8, u16, u32, u64, u128>.into_function(),
|func| {
func.with_overload(complex::<i16, i32, i64, i128, u8, u16, u32, u64, u128, i8>)
.with_overload(complex::<i32, i64, i128, u8, u16, u32, u64, u128, i8, i16>)
.with_overload(complex::<i64, i128, u8, u16, u32, u64, u128, i8, i16, i32>)
},
BatchSize::SmallInput,
);
})
.bench_function("10_simple_overload", |b| {
b.iter_batched(
|| add::<i8>.into_function(),
|func| {
func.with_overload(add::<i16>)
.with_overload(add::<i32>)
.with_overload(add::<i64>)
.with_overload(add::<i128>)
.with_overload(add::<u8>)
.with_overload(add::<u16>)
.with_overload(add::<u32>)
.with_overload(add::<u64>)
.with_overload(add::<u128>)
},
BatchSize::SmallInput,
);
})
.bench_function("10_complex_overload", |b| {
b.iter_batched(
|| complex::<i8, i16, i32, i64, i128, u8, u16, u32, u64, u128>.into_function(),
|func| {
func.with_overload(complex::<i16, i32, i64, i128, u8, u16, u32, u64, u128, i8>)
.with_overload(complex::<i32, i64, i128, u8, u16, u32, u64, u128, i8, i16>)
.with_overload(complex::<i64, i128, u8, u16, u32, u64, u128, i8, i16, i32>)
.with_overload(complex::<i128, u8, u16, u32, u64, u128, i8, i16, i32, i64>)
.with_overload(complex::<u8, u16, u32, u64, u128, i8, i16, i32, i64, i128>)
.with_overload(complex::<u16, u32, u64, u128, i8, i16, i32, i64, i128, u8>)
.with_overload(complex::<u32, u64, u128, i8, i16, i32, i64, i128, u8, u16>)
.with_overload(complex::<u64, u128, i8, i16, i32, i64, i128, u8, u16, u32>)
.with_overload(complex::<u128, i8, i16, i32, i64, i128, u8, u16, u32, u64>)
},
BatchSize::SmallInput,
);
})
.bench_function("01_nested_simple_overload", |b| {
b.iter_batched(
|| add::<i8>.into_function(),
|func| func.with_overload(add::<i16>),
BatchSize::SmallInput,
);
})
.bench_function("03_nested_simple_overload", |b| {
b.iter_batched(
|| add::<i8>.into_function(),
|func| {
func.with_overload(
add::<i16>
.into_function()
.with_overload(add::<i32>.into_function().with_overload(add::<i64>)),
)
},
BatchSize::SmallInput,
);
})
.bench_function("10_nested_simple_overload", |b| {
b.iter_batched(
|| add::<i8>.into_function(),
|func| {
func.with_overload(
add::<i16>.into_function().with_overload(
add::<i32>.into_function().with_overload(
add::<i64>.into_function().with_overload(
add::<i128>.into_function().with_overload(
add::<u8>.into_function().with_overload(
add::<u16>.into_function().with_overload(
add::<u32>.into_function().with_overload(
add::<u64>
.into_function()
.with_overload(add::<u128>),
),
),
),
),
),
),
),
)
},
BatchSize::SmallInput,
);
});
c.benchmark_group("call_overload")
.bench_function("01_simple_overload", |b| {
b.iter_batched(
|| {
(
add::<i8>.into_function().with_overload(add::<i16>),
ArgList::new().push_owned(75_i8).push_owned(25_i8),
)
},
|(func, args)| func.call(args),
BatchSize::SmallInput,
);
})
.bench_function("01_complex_overload", |b| {
b.iter_batched(
|| {
(
complex::<i8, i16, i32, i64, i128, u8, u16, u32, u64, u128>
.into_function()
.with_overload(
complex::<i16, i32, i64, i128, u8, u16, u32, u64, u128, i8>,
),
ArgList::new()
.push_owned(1_i8)
.push_owned(2_i16)
.push_owned(3_i32)
.push_owned(4_i64)
.push_owned(5_i128)
.push_owned(6_u8)
.push_owned(7_u16)
.push_owned(8_u32)
.push_owned(9_u64)
.push_owned(10_u128),
)
},
|(func, args)| func.call(args),
BatchSize::SmallInput,
);
})
.bench_function("03_simple_overload", |b| {
b.iter_batched(
|| {
(
add::<i8>
.into_function()
.with_overload(add::<i16>)
.with_overload(add::<i32>)
.with_overload(add::<i64>),
ArgList::new().push_owned(75_i32).push_owned(25_i32),
)
},
|(func, args)| func.call(args),
BatchSize::SmallInput,
);
})
.bench_function("03_complex_overload", |b| {
b.iter_batched(
|| {
(
complex::<i8, i16, i32, i64, i128, u8, u16, u32, u64, u128>
.into_function()
.with_overload(
complex::<i16, i32, i64, i128, u8, u16, u32, u64, u128, i8>,
)
.with_overload(
complex::<i32, i64, i128, u8, u16, u32, u64, u128, i8, i16>,
)
.with_overload(
complex::<i64, i128, u8, u16, u32, u64, u128, i8, i16, i32>,
),
ArgList::new()
.push_owned(1_i32)
.push_owned(2_i64)
.push_owned(3_i128)
.push_owned(4_u8)
.push_owned(5_u16)
.push_owned(6_u32)
.push_owned(7_u64)
.push_owned(8_u128)
.push_owned(9_i8)
.push_owned(10_i16),
)
},
|(func, args)| func.call(args),
BatchSize::SmallInput,
);
})
.bench_function("10_simple_overload", |b| {
b.iter_batched(
|| {
(
add::<i8>
.into_function()
.with_overload(add::<i16>)
.with_overload(add::<i32>)
.with_overload(add::<i64>)
.with_overload(add::<i128>)
.with_overload(add::<u8>)
.with_overload(add::<u16>)
.with_overload(add::<u32>)
.with_overload(add::<u64>)
.with_overload(add::<u128>),
ArgList::new().push_owned(75_u8).push_owned(25_u8),
)
},
|(func, args)| func.call(args),
BatchSize::SmallInput,
);
})
.bench_function("10_complex_overload", |b| {
b.iter_batched(
|| {
(
complex::<i8, i16, i32, i64, i128, u8, u16, u32, u64, u128>
.into_function()
.with_overload(
complex::<i16, i32, i64, i128, u8, u16, u32, u64, u128, i8>,
)
.with_overload(
complex::<i32, i64, i128, u8, u16, u32, u64, u128, i8, i16>,
)
.with_overload(
complex::<i64, i128, u8, u16, u32, u64, u128, i8, i16, i32>,
)
.with_overload(
complex::<i128, u8, u16, u32, u64, u128, i8, i16, i32, i64>,
)
.with_overload(
complex::<u8, u16, u32, u64, u128, i8, i16, i32, i64, i128>,
)
.with_overload(
complex::<u16, u32, u64, u128, i8, i16, i32, i64, i128, u8>,
)
.with_overload(
complex::<u32, u64, u128, i8, i16, i32, i64, i128, u8, u16>,
)
.with_overload(
complex::<u64, u128, i8, i16, i32, i64, i128, u8, u16, u32>,
)
.with_overload(
complex::<u128, i8, i16, i32, i64, i128, u8, u16, u32, u64>,
),
ArgList::new()
.push_owned(1_u8)
.push_owned(2_u16)
.push_owned(3_u32)
.push_owned(4_u64)
.push_owned(5_u128)
.push_owned(6_i8)
.push_owned(7_i16)
.push_owned(8_i32)
.push_owned(9_i64)
.push_owned(10_i128),
)
},
|(func, args)| func.call(args),
BatchSize::SmallInput,
);
});
}
fn clone(c: &mut Criterion) { fn clone(c: &mut Criterion) {
c.benchmark_group("clone").bench_function("function", |b| { c.benchmark_group("clone").bench_function("function", |b| {
let add = add.into_function(); let add = add.into_function();

View file

@ -183,6 +183,14 @@ impl<'a> Arg<'a> {
} }
} }
} }
/// Returns `true` if the argument is of type `T`.
pub fn is<T: TypePath>(&self) -> bool {
self.value
.try_as_reflect()
.map(<dyn Reflect>::is::<T>)
.unwrap_or_default()
}
} }
/// Represents an argument that can be passed to a [`DynamicFunction`] or [`DynamicFunctionMut`]. /// Represents an argument that can be passed to a [`DynamicFunction`] or [`DynamicFunctionMut`].

View file

@ -0,0 +1,311 @@
use crate::func::args::ArgCountOutOfBoundsError;
use core::fmt::{Debug, Formatter};
/// A container for zero or more argument counts for a function.
///
/// For most functions, this will contain a single count,
/// however, overloaded functions may contain more.
///
/// # Maximum Argument Count
///
/// The maximum number of arguments that can be represented by this struct is 63,
/// as given by [`ArgCount::MAX_COUNT`].
/// The reason for this is that all counts are stored internally as a single `u64`
/// with each bit representing a specific count based on its bit index.
///
/// This allows for a smaller memory footprint and faster lookups compared to a
/// `HashSet` or `Vec` of possible counts.
/// It's also more appropriate for representing the argument counts of a function
/// given that most functions will not have more than a few arguments.
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct ArgCount {
/// The bits representing the argument counts.
///
/// Each bit represents a specific count based on its bit index.
bits: u64,
/// The total number of argument counts.
len: u8,
}
impl ArgCount {
/// The maximum number of arguments that can be represented by this struct.
pub const MAX_COUNT: usize = u64::BITS as usize - 1;
/// Create a new [`ArgCount`] with the given count.
///
/// # Errors
///
/// Returns an error if the count is greater than [`Self::MAX_COUNT`].
pub fn new(count: usize) -> Result<Self, ArgCountOutOfBoundsError> {
Ok(Self {
bits: 1 << Self::try_to_u8(count)?,
len: 1,
})
}
/// Adds the given count to this [`ArgCount`].
///
/// # Panics
///
/// Panics if the count is greater than [`Self::MAX_COUNT`].
pub fn add(&mut self, count: usize) {
self.try_add(count).unwrap();
}
/// Attempts to add the given count to this [`ArgCount`].
///
/// # Errors
///
/// Returns an error if the count is greater than [`Self::MAX_COUNT`].
pub fn try_add(&mut self, count: usize) -> Result<(), ArgCountOutOfBoundsError> {
let count = Self::try_to_u8(count)?;
if !self.contains_unchecked(count) {
self.len += 1;
self.bits |= 1 << count;
}
Ok(())
}
/// Removes the given count from this [`ArgCount`].
pub fn remove(&mut self, count: usize) {
self.try_remove(count).unwrap();
}
/// Attempts to remove the given count from this [`ArgCount`].
///
/// # Errors
///
/// Returns an error if the count is greater than [`Self::MAX_COUNT`].
pub fn try_remove(&mut self, count: usize) -> Result<(), ArgCountOutOfBoundsError> {
let count = Self::try_to_u8(count)?;
if self.contains_unchecked(count) {
self.len -= 1;
self.bits &= !(1 << count);
}
Ok(())
}
/// Checks if this [`ArgCount`] contains the given count.
pub fn contains(&self, count: usize) -> bool {
count < usize::BITS as usize && (self.bits >> count) & 1 == 1
}
/// Returns the total number of argument counts that this [`ArgCount`] contains.
pub fn len(&self) -> usize {
self.len as usize
}
/// Returns true if this [`ArgCount`] contains no argument counts.
pub fn is_empty(&self) -> bool {
self.len == 0
}
/// Returns an iterator over the argument counts in this [`ArgCount`].
pub fn iter(&self) -> ArgCountIter {
ArgCountIter {
count: *self,
index: 0,
found: 0,
}
}
/// Checks if this [`ArgCount`] contains the given count without any bounds checking.
///
/// # Panics
///
/// Panics if the count is greater than [`Self::MAX_COUNT`].
fn contains_unchecked(&self, count: u8) -> bool {
(self.bits >> count) & 1 == 1
}
/// Attempts to convert the given count to a `u8` within the bounds of the [maximum count].
///
/// [maximum count]: Self::MAX_COUNT
fn try_to_u8(count: usize) -> Result<u8, ArgCountOutOfBoundsError> {
if count > Self::MAX_COUNT {
Err(ArgCountOutOfBoundsError(count))
} else {
Ok(count as u8)
}
}
}
/// Defaults this [`ArgCount`] to empty.
///
/// This means that it contains no argument counts, including zero.
impl Default for ArgCount {
fn default() -> Self {
Self { bits: 0, len: 0 }
}
}
impl Debug for ArgCount {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_set().entries(self.iter()).finish()
}
}
/// An iterator for the argument counts in an [`ArgCount`].
pub struct ArgCountIter {
count: ArgCount,
index: u8,
found: u8,
}
impl Iterator for ArgCountIter {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.index as usize > ArgCount::MAX_COUNT {
return None;
}
if self.found == self.count.len {
// All counts have been found
return None;
}
if self.count.contains_unchecked(self.index) {
self.index += 1;
self.found += 1;
return Some(self.index as usize - 1);
}
self.index += 1;
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.count.len(), Some(self.count.len()))
}
}
impl ExactSizeIterator for ArgCountIter {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_default_to_empty() {
let count = ArgCount::default();
assert_eq!(count.len(), 0);
assert!(count.is_empty());
assert!(!count.contains(0));
}
#[test]
fn should_construct_with_count() {
let count = ArgCount::new(3).unwrap();
assert_eq!(count.len(), 1);
assert!(!count.is_empty());
assert!(count.contains(3));
}
#[test]
fn should_add_count() {
let mut count = ArgCount::default();
count.add(3);
assert_eq!(count.len(), 1);
assert!(count.contains(3));
}
#[test]
fn should_add_multiple_counts() {
let mut count = ArgCount::default();
count.add(3);
count.add(5);
count.add(7);
assert_eq!(count.len(), 3);
assert!(!count.contains(0));
assert!(!count.contains(1));
assert!(!count.contains(2));
assert!(count.contains(3));
assert!(count.contains(5));
assert!(count.contains(7));
}
#[test]
fn should_add_idempotently() {
let mut count = ArgCount::default();
count.add(3);
count.add(3);
assert_eq!(count.len(), 1);
assert!(count.contains(3));
}
#[test]
fn should_remove_count() {
let mut count = ArgCount::default();
count.add(3);
assert_eq!(count.len(), 1);
assert!(count.contains(3));
count.remove(3);
assert_eq!(count.len(), 0);
assert!(!count.contains(3));
}
#[test]
fn should_allow_removeting_nonexistent_count() {
let mut count = ArgCount::default();
assert_eq!(count.len(), 0);
assert!(!count.contains(3));
count.remove(3);
assert_eq!(count.len(), 0);
assert!(!count.contains(3));
}
#[test]
fn should_iterate_over_counts() {
let mut count = ArgCount::default();
count.add(3);
count.add(5);
count.add(7);
let mut iter = count.iter();
assert_eq!(iter.len(), 3);
assert_eq!(iter.next(), Some(3));
assert_eq!(iter.next(), Some(5));
assert_eq!(iter.next(), Some(7));
assert_eq!(iter.next(), None);
}
#[test]
fn should_return_error_for_out_of_bounds_count() {
let count = ArgCount::new(64);
assert_eq!(count, Err(ArgCountOutOfBoundsError(64)));
let mut count = ArgCount::default();
assert_eq!(count.try_add(64), Err(ArgCountOutOfBoundsError(64)));
assert_eq!(count.try_remove(64), Err(ArgCountOutOfBoundsError(64)));
}
#[test]
fn should_return_false_for_out_of_bounds_contains() {
let count = ArgCount::default();
assert!(!count.contains(64));
}
}

View file

@ -32,3 +32,8 @@ pub enum ArgError {
#[error("expected an argument but received none")] #[error("expected an argument but received none")]
EmptyArgList, EmptyArgList,
} }
/// The given argument count is out of bounds.
#[derive(Debug, Error, PartialEq)]
#[error("argument count out of bounds: {0}")]
pub struct ArgCountOutOfBoundsError(pub usize);

View file

@ -5,7 +5,10 @@ use crate::{
}, },
PartialReflect, Reflect, TypePath, PartialReflect, Reflect, TypePath,
}; };
use alloc::{boxed::Box, collections::VecDeque}; use alloc::{
boxed::Box,
collections::vec_deque::{Iter, VecDeque},
};
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::{boxed::Box, format, vec}; use alloc::{boxed::Box, format, vec};
@ -286,6 +289,11 @@ impl<'a> ArgList<'a> {
self.pop_arg()?.take_mut() self.pop_arg()?.take_mut()
} }
/// Returns an iterator over the arguments in the list.
pub fn iter(&self) -> Iter<'_, Arg<'a>> {
self.list.iter()
}
/// Returns the number of arguments in the list. /// Returns the number of arguments in the list.
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.list.len() self.list.len()

View file

@ -4,6 +4,7 @@
//! [`DynamicFunctionMut`]: crate::func::DynamicFunctionMut //! [`DynamicFunctionMut`]: crate::func::DynamicFunctionMut
pub use arg::*; pub use arg::*;
pub use count::*;
pub use error::*; pub use error::*;
pub use from_arg::*; pub use from_arg::*;
pub use info::*; pub use info::*;
@ -11,6 +12,7 @@ pub use list::*;
pub use ownership::*; pub use ownership::*;
mod arg; mod arg;
mod count;
mod error; mod error;
mod from_arg; mod from_arg;
mod info; mod info;

View file

@ -2,8 +2,11 @@ use crate::{
self as bevy_reflect, self as bevy_reflect,
__macro_exports::RegisterForReflection, __macro_exports::RegisterForReflection,
func::{ func::{
args::ArgList, info::FunctionInfo, DynamicFunctionMut, Function, FunctionError, args::{ArgCount, ArgList},
FunctionResult, IntoFunction, IntoFunctionMut, dynamic_function_internal::DynamicFunctionInternal,
info::FunctionInfo,
DynamicFunctionMut, Function, FunctionOverloadError, FunctionResult, IntoFunction,
IntoFunctionMut,
}, },
serde::Serializable, serde::Serializable,
ApplyError, MaybeTyped, PartialReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, ApplyError, MaybeTyped, PartialReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned,
@ -16,6 +19,16 @@ use core::fmt::{Debug, Formatter};
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::{boxed::Box, format, vec}; use alloc::{boxed::Box, format, vec};
/// An [`Arc`] containing a callback to a reflected function.
///
/// The `Arc` is used to both ensure that it is `Send + Sync`
/// and to allow for the callback to be cloned.
///
/// Note that cloning is okay since we only ever need an immutable reference
/// to call a `dyn Fn` function.
/// If we were to contain a `dyn FnMut` instead, cloning would be a lot more complicated.
type ArcFn<'env> = Arc<dyn for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'env>;
/// A dynamic representation of a function. /// A dynamic representation of a function.
/// ///
/// This type can be used to represent any callable that satisfies [`Fn`] /// This type can be used to represent any callable that satisfies [`Fn`]
@ -35,7 +48,7 @@ use alloc::{boxed::Box, format, vec};
/// Most of the time, a [`DynamicFunction`] can be created using the [`IntoFunction`] trait: /// Most of the time, a [`DynamicFunction`] can be created using the [`IntoFunction`] trait:
/// ///
/// ``` /// ```
/// # use bevy_reflect::func::{ArgList, DynamicFunction, FunctionInfo, IntoFunction}; /// # use bevy_reflect::func::{ArgList, DynamicFunction, IntoFunction};
/// # /// #
/// fn add(a: i32, b: i32) -> i32 { /// fn add(a: i32, b: i32) -> i32 {
/// a + b /// a + b
@ -54,9 +67,9 @@ use alloc::{boxed::Box, format, vec};
/// ///
/// [`ReflectFn`]: crate::func::ReflectFn /// [`ReflectFn`]: crate::func::ReflectFn
/// [module-level documentation]: crate::func /// [module-level documentation]: crate::func
#[derive(Clone)]
pub struct DynamicFunction<'env> { pub struct DynamicFunction<'env> {
pub(super) info: FunctionInfo, pub(super) internal: DynamicFunctionInternal<ArcFn<'env>>,
pub(super) func: Arc<dyn for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'env>,
} }
impl<'env> DynamicFunction<'env> { impl<'env> DynamicFunction<'env> {
@ -65,17 +78,26 @@ impl<'env> DynamicFunction<'env> {
/// The given function can be used to call out to any other callable, /// The given function can be used to call out to any other callable,
/// including functions, closures, or methods. /// including functions, closures, or methods.
/// ///
/// It's important that the function signature matches the provided [`FunctionInfo`] /// It's important that the function signature matches the provided [`FunctionInfo`].
/// as this will be used to validate arguments when [calling] the function. /// as this will be used to validate arguments when [calling] the function.
/// This is also required in order for [function overloading] to work correctly.
/// ///
/// [calling]: DynamicFunction::call /// # Panics
///
/// This function may panic for any of the following reasons:
/// - No [`SignatureInfo`] is provided.
/// - A provided [`SignatureInfo`] has more arguments than [`ArgCount::MAX_COUNT`].
/// - The conversion to [`FunctionInfo`] fails.
///
/// [calling]: crate::func::dynamic_function::DynamicFunction::call
/// [`SignatureInfo`]: crate::func::SignatureInfo
/// [function overloading]: Self::with_overload
pub fn new<F: for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'env>( pub fn new<F: for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'env>(
func: F, func: F,
info: FunctionInfo, info: impl TryInto<FunctionInfo, Error: Debug>,
) -> Self { ) -> Self {
Self { Self {
info, internal: DynamicFunctionInternal::new(Arc::new(func), info.try_into().unwrap()),
func: Arc::new(func),
} }
} }
@ -88,10 +110,140 @@ impl<'env> DynamicFunction<'env> {
/// ///
/// [`DynamicFunctions`]: DynamicFunction /// [`DynamicFunctions`]: DynamicFunction
pub fn with_name(mut self, name: impl Into<Cow<'static, str>>) -> Self { pub fn with_name(mut self, name: impl Into<Cow<'static, str>>) -> Self {
self.info = self.info.with_name(name); self.internal = self.internal.with_name(name);
self self
} }
/// Add an overload to this function.
///
/// Overloads allow a single [`DynamicFunction`] to represent multiple functions of different signatures.
///
/// This can be used to handle multiple monomorphizations of a generic function
/// or to allow functions with a variable number of arguments.
///
/// Any functions with the same [argument signature] will be overwritten by the one from the new function, `F`.
/// For example, if the existing function had the signature `(i32, i32) -> i32`,
/// and the new function, `F`, also had the signature `(i32, i32) -> i32`,
/// the one from `F` would replace the one from the existing function.
///
/// Overloaded functions retain the [name] of the original function.
///
/// # Panics
///
/// Panics if the function, `F`, contains a signature already found in this function.
///
/// For a non-panicking version, see [`try_with_overload`].
///
/// # Examples
///
/// ```
/// # use std::ops::Add;
/// # use bevy_reflect::func::{ArgList, IntoFunction};
/// #
/// fn add<T: Add<Output = T>>(a: T, b: T) -> T {
/// a + b
/// }
///
/// // Currently, the only generic type `func` supports is `i32`:
/// let mut func = add::<i32>.into_function();
///
/// // However, we can add an overload to handle `f32` as well:
/// func = func.with_overload(add::<f32>);
///
/// // Test `i32`:
/// let args = ArgList::default().push_owned(25_i32).push_owned(75_i32);
/// let result = func.call(args).unwrap().unwrap_owned();
/// assert_eq!(result.try_take::<i32>().unwrap(), 100);
///
/// // Test `f32`:
/// let args = ArgList::default().push_owned(25.0_f32).push_owned(75.0_f32);
/// let result = func.call(args).unwrap().unwrap_owned();
/// assert_eq!(result.try_take::<f32>().unwrap(), 100.0);
///```
///
/// ```
/// # use bevy_reflect::func::{ArgList, IntoFunction};
/// #
/// fn add_2(a: i32, b: i32) -> i32 {
/// a + b
/// }
///
/// fn add_3(a: i32, b: i32, c: i32) -> i32 {
/// a + b + c
/// }
///
/// // Currently, `func` only supports two arguments.
/// let mut func = add_2.into_function();
///
/// // However, we can add an overload to handle three arguments as well.
/// func = func.with_overload(add_3);
///
/// // Test two arguments:
/// let args = ArgList::default().push_owned(25_i32).push_owned(75_i32);
/// let result = func.call(args).unwrap().unwrap_owned();
/// assert_eq!(result.try_take::<i32>().unwrap(), 100);
///
/// // Test three arguments:
/// let args = ArgList::default()
/// .push_owned(25_i32)
/// .push_owned(75_i32)
/// .push_owned(100_i32);
/// let result = func.call(args).unwrap().unwrap_owned();
/// assert_eq!(result.try_take::<i32>().unwrap(), 200);
/// ```
///
///```should_panic
/// # use bevy_reflect::func::IntoFunction;
///
/// fn add(a: i32, b: i32) -> i32 {
/// a + b
/// }
///
/// fn sub(a: i32, b: i32) -> i32 {
/// a - b
/// }
///
/// let mut func = add.into_function();
///
/// // This will panic because the function already has an argument signature for `(i32, i32)`:
/// func = func.with_overload(sub);
/// ```
///
/// [argument signature]: crate::func::signature::ArgumentSignature
/// [name]: Self::name
/// [`try_with_overload`]: Self::try_with_overload
pub fn with_overload<'a, F: IntoFunction<'a, Marker>, Marker>(
self,
function: F,
) -> DynamicFunction<'a>
where
'env: 'a,
{
self.try_with_overload(function).unwrap_or_else(|(_, err)| {
panic!("{}", err);
})
}
/// Attempt to add an overload to this function.
///
/// If the function, `F`, contains a signature already found in this function,
/// an error will be returned along with the original function.
///
/// For a panicking version, see [`with_overload`].
///
/// [`with_overload`]: Self::with_overload
pub fn try_with_overload<F: IntoFunction<'env, Marker>, Marker>(
mut self,
function: F,
) -> Result<Self, (Box<Self>, FunctionOverloadError)> {
let function = function.into_function();
match self.internal.merge(function.internal) {
Ok(_) => Ok(self),
Err(err) => Err((Box::new(self), err)),
}
}
/// Call the function with the given arguments. /// Call the function with the given arguments.
/// ///
/// # Example /// # Example
@ -116,25 +268,17 @@ impl<'env> DynamicFunction<'env> {
/// ///
/// The function itself may also return any errors it needs to. /// The function itself may also return any errors it needs to.
pub fn call<'a>(&self, args: ArgList<'a>) -> FunctionResult<'a> { pub fn call<'a>(&self, args: ArgList<'a>) -> FunctionResult<'a> {
let expected_arg_count = self.info.arg_count(); self.internal.validate_args(&args)?;
let received_arg_count = args.len(); let func = self.internal.get(&args)?;
func(args)
if expected_arg_count != received_arg_count {
Err(FunctionError::ArgCountMismatch {
expected: expected_arg_count,
received: received_arg_count,
})
} else {
(self.func)(args)
}
} }
/// Returns the function info. /// Returns the function info.
pub fn info(&self) -> &FunctionInfo { pub fn info(&self) -> &FunctionInfo {
&self.info self.internal.info()
} }
/// The [name] of the function. /// The name of the function.
/// ///
/// For [`DynamicFunctions`] created using [`IntoFunction`], /// For [`DynamicFunctions`] created using [`IntoFunction`],
/// the default name will always be the full path to the function as returned by [`core::any::type_name`], /// the default name will always be the full path to the function as returned by [`core::any::type_name`],
@ -143,17 +287,62 @@ impl<'env> DynamicFunction<'env> {
/// ///
/// This can be overridden using [`with_name`]. /// This can be overridden using [`with_name`].
/// ///
/// [name]: FunctionInfo::name /// If the function was [overloaded], it will retain its original name if it had one.
///
/// [`DynamicFunctions`]: DynamicFunction /// [`DynamicFunctions`]: DynamicFunction
/// [`with_name`]: Self::with_name /// [`with_name`]: Self::with_name
/// [overloaded]: Self::with_overload
pub fn name(&self) -> Option<&Cow<'static, str>> { pub fn name(&self) -> Option<&Cow<'static, str>> {
self.info.name() self.internal.name()
}
/// Returns `true` if the function is [overloaded].
///
/// # Example
///
/// ```
/// # use bevy_reflect::func::IntoFunction;
/// let add = (|a: i32, b: i32| a + b).into_function();
/// assert!(!add.is_overloaded());
///
/// let add = add.with_overload(|a: f32, b: f32| a + b);
/// assert!(add.is_overloaded());
/// ```
///
/// [overloaded]: Self::with_overload
pub fn is_overloaded(&self) -> bool {
self.internal.is_overloaded()
}
/// Returns the number of arguments the function expects.
///
/// For [overloaded] functions that can have a variable number of arguments,
/// this will contain the full set of counts for all signatures.
///
/// # Example
///
/// ```
/// # use bevy_reflect::func::IntoFunction;
/// let add = (|a: i32, b: i32| a + b).into_function();
/// assert!(add.arg_count().contains(2));
///
/// let add = add.with_overload(|a: f32, b: f32, c: f32| a + b + c);
/// assert!(add.arg_count().contains(2));
/// assert!(add.arg_count().contains(3));
/// ```
///
/// [overloaded]: Self::with_overload
pub fn arg_count(&self) -> ArgCount {
self.internal.arg_count()
} }
} }
impl Function for DynamicFunction<'static> { impl Function for DynamicFunction<'static> {
fn name(&self) -> Option<&Cow<'static, str>> {
self.internal.name()
}
fn info(&self) -> &FunctionInfo { fn info(&self) -> &FunctionInfo {
self.info() self.internal.info()
} }
fn reflect_call<'a>(&self, args: ArgList<'a>) -> FunctionResult<'a> { fn reflect_call<'a>(&self, args: ArgList<'a>) -> FunctionResult<'a> {
@ -258,32 +447,14 @@ impl_type_path!((in bevy_reflect) DynamicFunction<'env>);
/// This takes the format: `DynamicFunction(fn {name}({arg1}: {type1}, {arg2}: {type2}, ...) -> {return_type})`. /// This takes the format: `DynamicFunction(fn {name}({arg1}: {type1}, {arg2}: {type2}, ...) -> {return_type})`.
/// ///
/// Names for arguments and the function itself are optional and will default to `_` if not provided. /// Names for arguments and the function itself are optional and will default to `_` if not provided.
///
/// If the function is [overloaded], the output will include the signatures of all overloads as a set.
/// For example, `DynamicFunction(fn add{(_: i32, _: i32) -> i32, (_: f32, _: f32) -> f32})`.
///
/// [overloaded]: DynamicFunction::with_overload
impl<'env> Debug for DynamicFunction<'env> { impl<'env> Debug for DynamicFunction<'env> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let name = self.info.name().unwrap_or(&Cow::Borrowed("_")); write!(f, "DynamicFunction({:?})", &self.internal)
write!(f, "DynamicFunction(fn {name}(")?;
for (index, arg) in self.info.args().iter().enumerate() {
let name = arg.name().unwrap_or("_");
let ty = arg.type_path();
write!(f, "{name}: {ty}")?;
if index + 1 < self.info.args().len() {
write!(f, ", ")?;
}
}
let ret = self.info.return_info().type_path();
write!(f, ") -> {ret})")
}
}
impl<'env> Clone for DynamicFunction<'env> {
fn clone(&self) -> Self {
Self {
info: self.info.clone(),
func: Arc::clone(&self.func),
}
} }
} }
@ -304,15 +475,20 @@ impl<'env> IntoFunctionMut<'env, ()> for DynamicFunction<'env> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::func::IntoReturn; use crate::func::signature::ArgumentSignature;
use crate::func::{FunctionError, IntoReturn, SignatureInfo};
use crate::Type;
use bevy_utils::HashSet;
use core::ops::Add;
#[test] #[test]
fn should_overwrite_function_name() { fn should_overwrite_function_name() {
let c = 23; let c = 23;
let func = (|a: i32, b: i32| a + b + c) let func = (|a: i32, b: i32| a + b + c).into_function();
.into_function() assert!(func.name().is_none());
.with_name("my_function");
assert_eq!(func.info().name().unwrap(), "my_function"); let func = func.with_name("my_function");
assert_eq!(func.name().unwrap(), "my_function");
} }
#[test] #[test]
@ -332,13 +508,40 @@ mod tests {
let args = ArgList::default().push_owned(25_i32); let args = ArgList::default().push_owned(25_i32);
let error = func.call(args).unwrap_err(); let error = func.call(args).unwrap_err();
assert!(matches!(
assert_eq!(
error, error,
FunctionError::ArgCountMismatch { FunctionError::ArgCountMismatch {
expected: 2, expected: ArgCount::new(2).unwrap(),
received: 1 received: 1
} }
)); );
}
#[test]
fn should_return_error_on_arg_count_mismatch_overloaded() {
let func = (|a: i32, b: i32| a + b)
.into_function()
.with_overload(|a: i32, b: i32, c: i32| a + b + c);
let args = ArgList::default()
.push_owned(1_i32)
.push_owned(2_i32)
.push_owned(3_i32)
.push_owned(4_i32);
let error = func.call(args).unwrap_err();
let mut expected_count = ArgCount::new(2).unwrap();
expected_count.add(3);
assert_eq!(
error,
FunctionError::ArgCountMismatch {
expected: expected_count,
received: 4
}
);
} }
#[test] #[test]
@ -400,7 +603,7 @@ mod tests {
}, },
// The `FunctionInfo` doesn't really matter for this test // The `FunctionInfo` doesn't really matter for this test
// so we can just give it dummy information. // so we can just give it dummy information.
FunctionInfo::anonymous() SignatureInfo::anonymous()
.with_arg::<i32>("curr") .with_arg::<i32>("curr")
.with_arg::<()>("this"), .with_arg::<()>("this"),
); );
@ -409,4 +612,190 @@ mod tests {
let value = factorial.call(args).unwrap().unwrap_owned(); let value = factorial.call(args).unwrap().unwrap_owned();
assert_eq!(value.try_take::<i32>().unwrap(), 120); assert_eq!(value.try_take::<i32>().unwrap(), 120);
} }
#[test]
fn should_allow_creating_manual_generic_dynamic_function() {
let func = DynamicFunction::new(
|mut args| {
let a = args.take_arg()?;
let b = args.take_arg()?;
if a.is::<i32>() {
let a = a.take::<i32>()?;
let b = b.take::<i32>()?;
Ok((a + b).into_return())
} else {
let a = a.take::<f32>()?;
let b = b.take::<f32>()?;
Ok((a + b).into_return())
}
},
vec![
SignatureInfo::named("add::<i32>")
.with_arg::<i32>("a")
.with_arg::<i32>("b")
.with_return::<i32>(),
SignatureInfo::named("add::<f32>")
.with_arg::<f32>("a")
.with_arg::<f32>("b")
.with_return::<f32>(),
],
);
assert_eq!(func.name().unwrap(), "add::<i32>");
let func = func.with_name("add");
assert_eq!(func.name().unwrap(), "add");
let args = ArgList::default().push_owned(25_i32).push_owned(75_i32);
let result = func.call(args).unwrap().unwrap_owned();
assert_eq!(result.try_take::<i32>().unwrap(), 100);
let args = ArgList::default().push_owned(25.0_f32).push_owned(75.0_f32);
let result = func.call(args).unwrap().unwrap_owned();
assert_eq!(result.try_take::<f32>().unwrap(), 100.0);
}
#[test]
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: MissingSignature")]
fn should_panic_on_missing_function_info() {
let _ = DynamicFunction::new(|_| Ok(().into_return()), Vec::new());
}
#[test]
fn should_allow_function_overloading() {
fn add<T: Add<Output = T>>(a: T, b: T) -> T {
a + b
}
let func = add::<i32>.into_function().with_overload(add::<f32>);
let args = ArgList::default().push_owned(25_i32).push_owned(75_i32);
let result = func.call(args).unwrap().unwrap_owned();
assert_eq!(result.try_take::<i32>().unwrap(), 100);
let args = ArgList::default().push_owned(25.0_f32).push_owned(75.0_f32);
let result = func.call(args).unwrap().unwrap_owned();
assert_eq!(result.try_take::<f32>().unwrap(), 100.0);
}
#[test]
fn should_allow_variable_arguments_via_overloading() {
fn add_2(a: i32, b: i32) -> i32 {
a + b
}
fn add_3(a: i32, b: i32, c: i32) -> i32 {
a + b + c
}
let func = add_2.into_function().with_overload(add_3);
let args = ArgList::default().push_owned(25_i32).push_owned(75_i32);
let result = func.call(args).unwrap().unwrap_owned();
assert_eq!(result.try_take::<i32>().unwrap(), 100);
let args = ArgList::default()
.push_owned(25_i32)
.push_owned(75_i32)
.push_owned(100_i32);
let result = func.call(args).unwrap().unwrap_owned();
assert_eq!(result.try_take::<i32>().unwrap(), 200);
}
#[test]
fn should_allow_function_overloading_with_manual_overload() {
let manual = DynamicFunction::new(
|mut args| {
let a = args.take_arg()?;
let b = args.take_arg()?;
if a.is::<i32>() {
let a = a.take::<i32>()?;
let b = b.take::<i32>()?;
Ok((a + b).into_return())
} else {
let a = a.take::<f32>()?;
let b = b.take::<f32>()?;
Ok((a + b).into_return())
}
},
vec![
SignatureInfo::named("add::<i32>")
.with_arg::<i32>("a")
.with_arg::<i32>("b")
.with_return::<i32>(),
SignatureInfo::named("add::<f32>")
.with_arg::<f32>("a")
.with_arg::<f32>("b")
.with_return::<f32>(),
],
);
let func = manual.with_overload(|a: u32, b: u32| a + b);
let args = ArgList::default().push_owned(25_i32).push_owned(75_i32);
let result = func.call(args).unwrap().unwrap_owned();
assert_eq!(result.try_take::<i32>().unwrap(), 100);
let args = ArgList::default().push_owned(25_u32).push_owned(75_u32);
let result = func.call(args).unwrap().unwrap_owned();
assert_eq!(result.try_take::<u32>().unwrap(), 100);
}
#[test]
fn should_return_error_on_unknown_overload() {
fn add<T: Add<Output = T>>(a: T, b: T) -> T {
a + b
}
let func = add::<i32>.into_function().with_overload(add::<f32>);
let args = ArgList::default().push_owned(25_u32).push_owned(75_u32);
let result = func.call(args);
assert_eq!(
result.unwrap_err(),
FunctionError::NoOverload {
expected: HashSet::from([
ArgumentSignature::from_iter(vec![Type::of::<i32>(), Type::of::<i32>()]),
ArgumentSignature::from_iter(vec![Type::of::<f32>(), Type::of::<f32>()])
]),
received: ArgumentSignature::from_iter(vec![Type::of::<u32>(), Type::of::<u32>()]),
}
);
}
#[test]
fn should_debug_dynamic_function() {
fn greet(name: &String) -> String {
format!("Hello, {}!", name)
}
let function = greet.into_function();
let debug = format!("{:?}", function);
assert_eq!(debug, "DynamicFunction(fn bevy_reflect::func::dynamic_function::tests::should_debug_dynamic_function::greet(_: &alloc::string::String) -> alloc::string::String)");
}
#[test]
fn should_debug_anonymous_dynamic_function() {
let function = (|a: i32, b: i32| a + b).into_function();
let debug = format!("{:?}", function);
assert_eq!(debug, "DynamicFunction(fn _(_: i32, _: i32) -> i32)");
}
#[test]
fn should_debug_overloaded_dynamic_function() {
fn add<T: Add<Output = T>>(a: T, b: T) -> T {
a + b
}
let func = add::<i32>
.into_function()
.with_overload(add::<f32>)
.with_name("add");
let debug = format!("{:?}", func);
assert_eq!(
debug,
"DynamicFunction(fn add{(_: i32, _: i32) -> i32, (_: f32, _: f32) -> f32})"
);
}
} }

View file

@ -0,0 +1,378 @@
use crate::func::args::ArgCount;
use crate::func::signature::{ArgListSignature, ArgumentSignature};
use crate::func::{ArgList, FunctionError, FunctionInfo, FunctionOverloadError};
use alloc::borrow::Cow;
use bevy_utils::hashbrown::HashMap;
use core::fmt::{Debug, Formatter};
/// An internal structure for storing a function and its corresponding [function information].
///
/// This is used to facilitate the sharing of functionality between [`DynamicFunction`]
/// and [`DynamicFunctionMut`].
///
/// [function information]: FunctionInfo
/// [`DynamicFunction`]: crate::func::DynamicFunction
/// [`DynamicFunctionMut`]: crate::func::DynamicFunctionMut
#[derive(Clone)]
pub(super) struct DynamicFunctionInternal<F> {
functions: Vec<F>,
info: FunctionInfo,
arg_map: HashMap<ArgumentSignature, usize>,
}
impl<F> DynamicFunctionInternal<F> {
/// Create a new instance of [`DynamicFunctionInternal`] with the given function
/// and its corresponding information.
pub fn new(func: F, info: FunctionInfo) -> Self {
let arg_map = info
.signatures()
.iter()
.map(|sig| (ArgumentSignature::from(sig), 0))
.collect();
Self {
functions: vec![func],
info,
arg_map,
}
}
pub fn with_name(mut self, name: impl Into<Cow<'static, str>>) -> Self {
self.info = self.info.with_name(Some(name.into()));
self
}
/// The name of the function.
pub fn name(&self) -> Option<&Cow<'static, str>> {
self.info.name()
}
/// Returns `true` if the function is overloaded.
pub fn is_overloaded(&self) -> bool {
self.info.is_overloaded()
}
/// Get an immutable reference to the function.
///
/// If the function is not overloaded, it will always be returned regardless of the arguments.
/// Otherwise, the function will be selected based on the arguments provided.
///
/// If no overload matches the provided arguments, returns [`FunctionError::NoOverload`].
pub fn get(&self, args: &ArgList) -> Result<&F, FunctionError> {
if !self.info.is_overloaded() {
return Ok(&self.functions[0]);
}
let signature = ArgListSignature::from(args);
self.arg_map
.get(&signature)
.map(|index| &self.functions[*index])
.ok_or_else(|| FunctionError::NoOverload {
expected: self.arg_map.keys().cloned().collect(),
received: ArgumentSignature::from(args),
})
}
/// Get a mutable reference to the function.
///
/// If the function is not overloaded, it will always be returned regardless of the arguments.
/// Otherwise, the function will be selected based on the arguments provided.
///
/// If no overload matches the provided arguments, returns [`FunctionError::NoOverload`].
pub fn get_mut(&mut self, args: &ArgList) -> Result<&mut F, FunctionError> {
if !self.info.is_overloaded() {
return Ok(&mut self.functions[0]);
}
let signature = ArgListSignature::from(args);
self.arg_map
.get(&signature)
.map(|index| &mut self.functions[*index])
.ok_or_else(|| FunctionError::NoOverload {
expected: self.arg_map.keys().cloned().collect(),
received: ArgumentSignature::from(args),
})
}
/// Returns the function information contained in the map.
#[inline]
pub fn info(&self) -> &FunctionInfo {
&self.info
}
/// Returns the number of arguments the function expects.
///
/// For overloaded functions that can have a variable number of arguments,
/// this will contain the full set of counts for all signatures.
pub fn arg_count(&self) -> ArgCount {
self.info.arg_count()
}
/// Helper method for validating that a given set of arguments are _potentially_ valid for this function.
///
/// Currently, this validates:
/// - The number of arguments is within the expected range
pub fn validate_args(&self, args: &ArgList) -> Result<(), FunctionError> {
let expected_arg_count = self.arg_count();
let received_arg_count = args.len();
if !expected_arg_count.contains(received_arg_count) {
Err(FunctionError::ArgCountMismatch {
expected: expected_arg_count,
received: received_arg_count,
})
} else {
Ok(())
}
}
/// Merge another [`DynamicFunctionInternal`] into this one.
///
/// If `other` contains any functions with the same signature as this one,
/// an error will be returned along with the original, unchanged instance.
///
/// Therefore, this method should always return an overloaded function if the merge is successful.
///
/// Additionally, if the merge succeeds, it should be guaranteed that the order
/// of the functions in the map will be preserved.
/// For example, merging `[func_a, func_b]` (self) with `[func_c, func_d]` (other) should result in
/// `[func_a, func_b, func_c, func_d]`.
/// And merging `[func_c, func_d]` (self) with `[func_a, func_b]` (other) should result in
/// `[func_c, func_d, func_a, func_b]`.
pub fn merge(&mut self, mut other: Self) -> Result<(), FunctionOverloadError> {
// Keep a separate map of the new indices to avoid mutating the existing one
// until we can be sure the merge will be successful.
let mut new_signatures = HashMap::new();
for (sig, index) in other.arg_map {
if self.arg_map.contains_key(&sig) {
return Err(FunctionOverloadError::DuplicateSignature(sig));
}
new_signatures.insert_unique_unchecked(sig, self.functions.len() + index);
}
self.arg_map.reserve(new_signatures.len());
for (sig, index) in new_signatures {
self.arg_map.insert_unique_unchecked(sig, index);
}
self.functions.append(&mut other.functions);
self.info.extend_unchecked(other.info);
Ok(())
}
/// Maps the internally stored function(s) from type `F` to type `G`.
pub fn map_functions<G>(self, f: fn(F) -> G) -> DynamicFunctionInternal<G> {
DynamicFunctionInternal {
functions: self.functions.into_iter().map(f).collect(),
info: self.info,
arg_map: self.arg_map,
}
}
}
impl<F> Debug for DynamicFunctionInternal<F> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
self.info
.pretty_printer()
.include_fn_token()
.include_name()
.fmt(f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::func::{FunctionInfo, SignatureInfo};
use crate::Type;
#[test]
fn should_merge_single_into_single() {
let mut func_a = DynamicFunctionInternal::new(
'a',
FunctionInfo::new(SignatureInfo::anonymous().with_arg::<i8>("arg0")),
);
let func_b = DynamicFunctionInternal::new(
'b',
FunctionInfo::new(SignatureInfo::anonymous().with_arg::<u8>("arg0")),
);
func_a.merge(func_b).unwrap();
assert_eq!(func_a.functions, vec!['a', 'b']);
assert_eq!(func_a.info.signatures().len(), 2);
assert_eq!(
func_a.arg_map,
HashMap::from_iter([
(ArgumentSignature::from_iter([Type::of::<i8>()]), 0),
(ArgumentSignature::from_iter([Type::of::<u8>()]), 1),
])
);
}
#[test]
fn should_merge_single_into_overloaded() {
let mut func_a = DynamicFunctionInternal::new(
'a',
FunctionInfo::new(SignatureInfo::anonymous().with_arg::<i8>("arg0")),
);
let func_b = DynamicFunctionInternal {
functions: vec!['b', 'c'],
info: FunctionInfo::new(SignatureInfo::anonymous().with_arg::<u8>("arg0"))
.with_overload(SignatureInfo::anonymous().with_arg::<u16>("arg0"))
.unwrap(),
arg_map: HashMap::from_iter([
(ArgumentSignature::from_iter([Type::of::<u8>()]), 0),
(ArgumentSignature::from_iter([Type::of::<u16>()]), 1),
]),
};
func_a.merge(func_b).unwrap();
assert_eq!(func_a.functions, vec!['a', 'b', 'c']);
assert_eq!(func_a.info.signatures().len(), 3);
assert_eq!(
func_a.arg_map,
HashMap::from_iter([
(ArgumentSignature::from_iter([Type::of::<i8>()]), 0),
(ArgumentSignature::from_iter([Type::of::<u8>()]), 1),
(ArgumentSignature::from_iter([Type::of::<u16>()]), 2),
])
);
}
#[test]
fn should_merge_overloaed_into_single() {
let mut func_a = DynamicFunctionInternal {
functions: vec!['a', 'b'],
info: FunctionInfo::new(SignatureInfo::anonymous().with_arg::<i8>("arg0"))
.with_overload(SignatureInfo::anonymous().with_arg::<i16>("arg0"))
.unwrap(),
arg_map: HashMap::from_iter([
(ArgumentSignature::from_iter([Type::of::<i8>()]), 0),
(ArgumentSignature::from_iter([Type::of::<i16>()]), 1),
]),
};
let func_b = DynamicFunctionInternal::new(
'c',
FunctionInfo::new(SignatureInfo::anonymous().with_arg::<u8>("arg0")),
);
func_a.merge(func_b).unwrap();
assert_eq!(func_a.functions, vec!['a', 'b', 'c']);
assert_eq!(func_a.info.signatures().len(), 3);
assert_eq!(
func_a.arg_map,
HashMap::from_iter([
(ArgumentSignature::from_iter([Type::of::<i8>()]), 0),
(ArgumentSignature::from_iter([Type::of::<i16>()]), 1),
(ArgumentSignature::from_iter([Type::of::<u8>()]), 2),
])
);
}
#[test]
fn should_merge_overloaded_into_overloaded() {
let mut func_a = DynamicFunctionInternal {
functions: vec!['a', 'b'],
info: FunctionInfo::new(SignatureInfo::anonymous().with_arg::<i8>("arg0"))
.with_overload(SignatureInfo::anonymous().with_arg::<i16>("arg0"))
.unwrap(),
arg_map: HashMap::from_iter([
(ArgumentSignature::from_iter([Type::of::<i8>()]), 0),
(ArgumentSignature::from_iter([Type::of::<i16>()]), 1),
]),
};
let func_b = DynamicFunctionInternal {
functions: vec!['c', 'd'],
info: FunctionInfo::new(SignatureInfo::anonymous().with_arg::<u8>("arg0"))
.with_overload(SignatureInfo::anonymous().with_arg::<u16>("arg0"))
.unwrap(),
arg_map: HashMap::from_iter([
(ArgumentSignature::from_iter([Type::of::<u8>()]), 0),
(ArgumentSignature::from_iter([Type::of::<u16>()]), 1),
]),
};
func_a.merge(func_b).unwrap();
assert_eq!(func_a.functions, vec!['a', 'b', 'c', 'd']);
assert_eq!(func_a.info.signatures().len(), 4);
assert_eq!(
func_a.arg_map,
HashMap::from_iter([
(ArgumentSignature::from_iter([Type::of::<i8>()]), 0),
(ArgumentSignature::from_iter([Type::of::<i16>()]), 1),
(ArgumentSignature::from_iter([Type::of::<u8>()]), 2),
(ArgumentSignature::from_iter([Type::of::<u16>()]), 3),
])
);
}
#[test]
fn should_return_error_on_duplicate_signature() {
let mut func_a = DynamicFunctionInternal::new(
'a',
FunctionInfo::new(
SignatureInfo::anonymous()
.with_arg::<i8>("arg0")
.with_arg::<i16>("arg1"),
),
);
let func_b = DynamicFunctionInternal {
functions: vec!['b', 'c'],
info: FunctionInfo::new(
SignatureInfo::anonymous()
.with_arg::<u8>("arg0")
.with_arg::<u16>("arg1"),
)
.with_overload(
SignatureInfo::anonymous()
.with_arg::<i8>("arg0")
.with_arg::<i16>("arg1"),
)
.unwrap(),
arg_map: HashMap::from_iter([
(
ArgumentSignature::from_iter([Type::of::<u8>(), Type::of::<u16>()]),
0,
),
(
ArgumentSignature::from_iter([Type::of::<i8>(), Type::of::<i16>()]),
1,
),
]),
};
let FunctionOverloadError::DuplicateSignature(duplicate) =
func_a.merge(func_b).unwrap_err()
else {
panic!("Expected `FunctionOverloadError::DuplicateSignature`");
};
assert_eq!(
duplicate,
ArgumentSignature::from_iter([Type::of::<i8>(), Type::of::<i16>()])
);
// Assert the original remains unchanged:
assert!(!func_a.is_overloaded());
assert_eq!(func_a.functions, vec!['a']);
assert_eq!(func_a.info.signatures().len(), 1);
assert_eq!(
func_a.arg_map,
HashMap::from_iter([(
ArgumentSignature::from_iter([Type::of::<i8>(), Type::of::<i16>()]),
0
),])
);
}
}

View file

@ -1,14 +1,18 @@
use alloc::{borrow::Cow, boxed::Box}; use alloc::{borrow::Cow, boxed::Box, sync::Arc};
use core::fmt::{Debug, Formatter}; use core::fmt::{Debug, Formatter};
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::{boxed::Box, format, vec}; use alloc::{boxed::Box, format, vec};
use crate::func::{ use crate::func::{
args::ArgList, info::FunctionInfo, DynamicFunction, FunctionError, FunctionResult, args::{ArgCount, ArgList},
IntoFunctionMut, dynamic_function_internal::DynamicFunctionInternal,
DynamicFunction, FunctionInfo, FunctionOverloadError, FunctionResult, IntoFunctionMut,
}; };
/// A [`Box`] containing a callback to a reflected function.
type BoxFnMut<'env> = Box<dyn for<'a> FnMut(ArgList<'a>) -> FunctionResult<'a> + 'env>;
/// A dynamic representation of a function. /// A dynamic representation of a function.
/// ///
/// This type can be used to represent any callable that satisfies [`FnMut`] /// This type can be used to represent any callable that satisfies [`FnMut`]
@ -66,8 +70,7 @@ use crate::func::{
/// [`ReflectFnMut`]: crate::func::ReflectFnMut /// [`ReflectFnMut`]: crate::func::ReflectFnMut
/// [module-level documentation]: crate::func /// [module-level documentation]: crate::func
pub struct DynamicFunctionMut<'env> { pub struct DynamicFunctionMut<'env> {
info: FunctionInfo, internal: DynamicFunctionInternal<BoxFnMut<'env>>,
func: Box<dyn for<'a> FnMut(ArgList<'a>) -> FunctionResult<'a> + 'env>,
} }
impl<'env> DynamicFunctionMut<'env> { impl<'env> DynamicFunctionMut<'env> {
@ -76,17 +79,26 @@ impl<'env> DynamicFunctionMut<'env> {
/// The given function can be used to call out to any other callable, /// The given function can be used to call out to any other callable,
/// including functions, closures, or methods. /// including functions, closures, or methods.
/// ///
/// It's important that the function signature matches the provided [`FunctionInfo`] /// It's important that the function signature matches the provided [`FunctionInfo`].
/// as this will be used to validate arguments when [calling] the function. /// as this will be used to validate arguments when [calling] the function.
/// This is also required in order for [function overloading] to work correctly.
/// ///
/// [calling]: DynamicFunctionMut::call /// # Panics
///
/// This function may panic for any of the following reasons:
/// - No [`SignatureInfo`] is provided.
/// - A provided [`SignatureInfo`] has more arguments than [`ArgCount::MAX_COUNT`].
/// - The conversion to [`FunctionInfo`] fails.
///
/// [calling]: crate::func::dynamic_function_mut::DynamicFunctionMut::call
/// [`SignatureInfo`]: crate::func::SignatureInfo
/// [function overloading]: Self::with_overload
pub fn new<F: for<'a> FnMut(ArgList<'a>) -> FunctionResult<'a> + 'env>( pub fn new<F: for<'a> FnMut(ArgList<'a>) -> FunctionResult<'a> + 'env>(
func: F, func: F,
info: FunctionInfo, info: impl TryInto<FunctionInfo, Error: Debug>,
) -> Self { ) -> Self {
Self { Self {
info, internal: DynamicFunctionInternal::new(Box::new(func), info.try_into().unwrap()),
func: Box::new(func),
} }
} }
@ -99,10 +111,99 @@ impl<'env> DynamicFunctionMut<'env> {
/// ///
/// [`DynamicFunctionMuts`]: DynamicFunctionMut /// [`DynamicFunctionMuts`]: DynamicFunctionMut
pub fn with_name(mut self, name: impl Into<Cow<'static, str>>) -> Self { pub fn with_name(mut self, name: impl Into<Cow<'static, str>>) -> Self {
self.info = self.info.with_name(name); self.internal = self.internal.with_name(name);
self self
} }
/// Add an overload to this function.
///
/// Overloads allow a single [`DynamicFunctionMut`] to represent multiple functions of different signatures.
///
/// This can be used to handle multiple monomorphizations of a generic function
/// or to allow functions with a variable number of arguments.
///
/// Any functions with the same [argument signature] will be overwritten by the one from the new function, `F`.
/// For example, if the existing function had the signature `(i32, i32) -> i32`,
/// and the new function, `F`, also had the signature `(i32, i32) -> i32`,
/// the one from `F` would replace the one from the existing function.
///
/// Overloaded functions retain the [name] of the original function.
///
/// Note that it may be impossible to overload closures that mutably borrow from their environment
/// due to Rust's borrowing rules.
/// However, it's still possible to overload functions that do not capture their environment mutably,
/// or those that maintain mutually exclusive mutable references to their environment.
///
/// # Panics
///
/// Panics if the function, `F`, contains a signature already found in this function.
///
/// For a non-panicking version, see [`try_with_overload`].
///
/// # Example
///
/// ```
/// # use bevy_reflect::func::IntoFunctionMut;
/// let mut total_i32 = 0;
/// let mut add_i32 = |a: i32| total_i32 += a;
///
/// let mut total_f32 = 0.0;
/// let mut add_f32 = |a: f32| total_f32 += a;
///
/// // Currently, the only generic type `func` supports is `i32`.
/// let mut func = add_i32.into_function_mut();
///
/// // However, we can add an overload to handle `f32` as well:
/// func = func.with_overload(add_f32);
///
/// // Test `i32`:
/// let args = bevy_reflect::func::ArgList::new().push_owned(123_i32);
/// func.call(args).unwrap();
///
/// // Test `f32`:
/// let args = bevy_reflect::func::ArgList::new().push_owned(1.23_f32);
/// func.call(args).unwrap();
///
/// drop(func);
/// assert_eq!(total_i32, 123);
/// assert_eq!(total_f32, 1.23);
/// ```
///
/// [argument signature]: crate::func::signature::ArgumentSignature
/// [name]: Self::name
/// [`try_with_overload`]: Self::try_with_overload
pub fn with_overload<'a, F: IntoFunctionMut<'a, Marker>, Marker>(
self,
function: F,
) -> DynamicFunctionMut<'a>
where
'env: 'a,
{
self.try_with_overload(function).unwrap_or_else(|(_, err)| {
panic!("{}", err);
})
}
/// Attempt to add an overload to this function.
///
/// If the function, `F`, contains a signature already found in this function,
/// an error will be returned along with the original function.
///
/// For a panicking version, see [`with_overload`].
///
/// [`with_overload`]: Self::with_overload
pub fn try_with_overload<F: IntoFunctionMut<'env, Marker>, Marker>(
mut self,
function: F,
) -> Result<Self, (Box<Self>, FunctionOverloadError)> {
let function = function.into_function_mut();
match self.internal.merge(function.internal) {
Ok(_) => Ok(self),
Err(err) => Err((Box::new(self), err)),
}
}
/// Call the function with the given arguments. /// Call the function with the given arguments.
/// ///
/// Variables that are captured mutably by this function /// Variables that are captured mutably by this function
@ -135,17 +236,9 @@ impl<'env> DynamicFunctionMut<'env> {
/// ///
/// [`call_once`]: DynamicFunctionMut::call_once /// [`call_once`]: DynamicFunctionMut::call_once
pub fn call<'a>(&mut self, args: ArgList<'a>) -> FunctionResult<'a> { pub fn call<'a>(&mut self, args: ArgList<'a>) -> FunctionResult<'a> {
let expected_arg_count = self.info.arg_count(); self.internal.validate_args(&args)?;
let received_arg_count = args.len(); let func = self.internal.get_mut(&args)?;
func(args)
if expected_arg_count != received_arg_count {
Err(FunctionError::ArgCountMismatch {
expected: expected_arg_count,
received: received_arg_count,
})
} else {
(self.func)(args)
}
} }
/// Call the function with the given arguments and consume it. /// Call the function with the given arguments and consume it.
@ -177,25 +270,15 @@ impl<'env> DynamicFunctionMut<'env> {
/// ///
/// The function itself may also return any errors it needs to. /// The function itself may also return any errors it needs to.
pub fn call_once(mut self, args: ArgList) -> FunctionResult { pub fn call_once(mut self, args: ArgList) -> FunctionResult {
let expected_arg_count = self.info.arg_count(); self.call(args)
let received_arg_count = args.len();
if expected_arg_count != received_arg_count {
Err(FunctionError::ArgCountMismatch {
expected: expected_arg_count,
received: received_arg_count,
})
} else {
(self.func)(args)
}
} }
/// Returns the function info. /// Returns the function info.
pub fn info(&self) -> &FunctionInfo { pub fn info(&self) -> &FunctionInfo {
&self.info self.internal.info()
} }
/// The [name] of the function. /// The name of the function.
/// ///
/// For [`DynamicFunctionMuts`] created using [`IntoFunctionMut`], /// For [`DynamicFunctionMuts`] created using [`IntoFunctionMut`],
/// the default name will always be the full path to the function as returned by [`core::any::type_name`], /// the default name will always be the full path to the function as returned by [`core::any::type_name`],
@ -204,11 +287,52 @@ impl<'env> DynamicFunctionMut<'env> {
/// ///
/// This can be overridden using [`with_name`]. /// This can be overridden using [`with_name`].
/// ///
/// [name]: FunctionInfo::name
/// [`DynamicFunctionMuts`]: DynamicFunctionMut /// [`DynamicFunctionMuts`]: DynamicFunctionMut
/// [`with_name`]: Self::with_name /// [`with_name`]: Self::with_name
pub fn name(&self) -> Option<&Cow<'static, str>> { pub fn name(&self) -> Option<&Cow<'static, str>> {
self.info.name() self.internal.name()
}
/// Returns `true` if the function is [overloaded].
///
/// # Example
///
/// ```
/// # use bevy_reflect::func::IntoFunctionMut;
/// let mut total_i32 = 0;
/// let increment = (|value: i32| total_i32 += value).into_function_mut();
/// assert!(!increment.is_overloaded());
///
/// let mut total_f32 = 0.0;
/// let increment = increment.with_overload(|value: f32| total_f32 += value);
/// assert!(increment.is_overloaded());
/// ```
///
/// [overloaded]: Self::with_overload
pub fn is_overloaded(&self) -> bool {
self.internal.is_overloaded()
}
/// Returns the number of arguments the function expects.
///
/// For [overloaded] functions that can have a variable number of arguments,
/// this will contain the full set of counts for all signatures.
///
/// # Example
///
/// ```
/// # use bevy_reflect::func::IntoFunctionMut;
/// let add = (|a: i32, b: i32| a + b).into_function_mut();
/// assert!(add.arg_count().contains(2));
///
/// let add = add.with_overload(|a: f32, b: f32, c: f32| a + b + c);
/// assert!(add.arg_count().contains(2));
/// assert!(add.arg_count().contains(3));
/// ```
///
/// [overloaded]: Self::with_overload
pub fn arg_count(&self) -> ArgCount {
self.internal.arg_count()
} }
} }
@ -217,23 +341,14 @@ impl<'env> DynamicFunctionMut<'env> {
/// This takes the format: `DynamicFunctionMut(fn {name}({arg1}: {type1}, {arg2}: {type2}, ...) -> {return_type})`. /// This takes the format: `DynamicFunctionMut(fn {name}({arg1}: {type1}, {arg2}: {type2}, ...) -> {return_type})`.
/// ///
/// Names for arguments and the function itself are optional and will default to `_` if not provided. /// Names for arguments and the function itself are optional and will default to `_` if not provided.
///
/// If the function is [overloaded], the output will include the signatures of all overloads as a set.
/// For example, `DynamicFunctionMut(fn add{(_: i32, _: i32) -> i32, (_: f32, _: f32) -> f32})`.
///
/// [overloaded]: DynamicFunctionMut::with_overload
impl<'env> Debug for DynamicFunctionMut<'env> { impl<'env> Debug for DynamicFunctionMut<'env> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let name = self.info.name().unwrap_or(&Cow::Borrowed("_")); write!(f, "DynamicFunctionMut({:?})", &self.internal)
write!(f, "DynamicFunctionMut(fn {name}(")?;
for (index, arg) in self.info.args().iter().enumerate() {
let name = arg.name().unwrap_or("_");
let ty = arg.type_path();
write!(f, "{name}: {ty}")?;
if index + 1 < self.info.args().len() {
write!(f, ", ")?;
}
}
let ret = self.info.return_info().type_path();
write!(f, ") -> {ret})")
} }
} }
@ -241,12 +356,20 @@ impl<'env> From<DynamicFunction<'env>> for DynamicFunctionMut<'env> {
#[inline] #[inline]
fn from(function: DynamicFunction<'env>) -> Self { fn from(function: DynamicFunction<'env>) -> Self {
Self { Self {
info: function.info, internal: function.internal.map_functions(arc_to_box),
func: Box::new(move |args| (function.func)(args)),
} }
} }
} }
/// Helper function from converting an [`Arc`] function to a [`Box`] function.
///
/// This is needed to help the compiler infer the correct types.
fn arc_to_box<'env>(
f: Arc<dyn for<'a> Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'env>,
) -> BoxFnMut<'env> {
Box::new(move |args| f(args))
}
impl<'env> IntoFunctionMut<'env, ()> for DynamicFunctionMut<'env> { impl<'env> IntoFunctionMut<'env, ()> for DynamicFunctionMut<'env> {
#[inline] #[inline]
fn into_function_mut(self) -> DynamicFunctionMut<'env> { fn into_function_mut(self) -> DynamicFunctionMut<'env> {
@ -257,14 +380,17 @@ impl<'env> IntoFunctionMut<'env, ()> for DynamicFunctionMut<'env> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::func::{FunctionError, IntoReturn, SignatureInfo};
use core::ops::Add;
#[test] #[test]
fn should_overwrite_function_name() { fn should_overwrite_function_name() {
let mut total = 0; let mut total = 0;
let func = (|a: i32, b: i32| total = a + b) let func = (|a: i32, b: i32| total = a + b).into_function_mut();
.into_function_mut() assert!(func.name().is_none());
.with_name("my_function");
assert_eq!(func.info().name().unwrap(), "my_function"); let func = func.with_name("my_function");
assert_eq!(func.name().unwrap(), "my_function");
} }
#[test] #[test]
@ -285,22 +411,79 @@ mod tests {
let args = ArgList::default().push_owned(25_i32); let args = ArgList::default().push_owned(25_i32);
let error = func.call(args).unwrap_err(); let error = func.call(args).unwrap_err();
assert!(matches!( assert_eq!(
error, error,
FunctionError::ArgCountMismatch { FunctionError::ArgCountMismatch {
expected: 2, expected: ArgCount::new(2).unwrap(),
received: 1 received: 1
} }
)); );
let args = ArgList::default().push_owned(25_i32); let args = ArgList::default().push_owned(25_i32);
let error = func.call_once(args).unwrap_err(); let error = func.call_once(args).unwrap_err();
assert!(matches!( assert_eq!(
error, error,
FunctionError::ArgCountMismatch { FunctionError::ArgCountMismatch {
expected: 2, expected: ArgCount::new(2).unwrap(),
received: 1 received: 1
} }
)); );
}
#[test]
fn should_allow_creating_manual_generic_dynamic_function_mut() {
let mut total = 0_i32;
let func = DynamicFunctionMut::new(
|mut args| {
let value = args.take_arg()?;
if value.is::<i32>() {
let value = value.take::<i32>()?;
total += value;
} else {
let value = value.take::<i16>()?;
total += value as i32;
}
Ok(().into_return())
},
vec![
SignatureInfo::named("add::<i32>").with_arg::<i32>("value"),
SignatureInfo::named("add::<i16>").with_arg::<i16>("value"),
],
);
assert_eq!(func.name().unwrap(), "add::<i32>");
let mut func = func.with_name("add");
assert_eq!(func.name().unwrap(), "add");
let args = ArgList::default().push_owned(25_i32);
func.call(args).unwrap();
let args = ArgList::default().push_owned(75_i16);
func.call(args).unwrap();
drop(func);
assert_eq!(total, 100);
}
// Closures that mutably borrow from their environment cannot realistically
// be overloaded since that would break Rust's borrowing rules.
// However, we still need to verify overloaded functions work since a
// `DynamicFunctionMut` can also be made from a non-mutably borrowing closure/function.
#[test]
fn should_allow_function_overloading() {
fn add<T: Add<Output = T>>(a: T, b: T) -> T {
a + b
}
let mut func = add::<i32>.into_function_mut().with_overload(add::<f32>);
let args = ArgList::default().push_owned(25_i32).push_owned(75_i32);
let result = func.call(args).unwrap().unwrap_owned();
assert_eq!(result.try_take::<i32>().unwrap(), 100);
let args = ArgList::default().push_owned(25.0_f32).push_owned(75.0_f32);
let result = func.call(args).unwrap().unwrap_owned();
assert_eq!(result.try_take::<f32>().unwrap(), 100.0);
} }
} }

View file

@ -1,5 +1,10 @@
use crate::func::{args::ArgError, Return}; use crate::func::signature::ArgumentSignature;
use crate::func::{
args::{ArgCount, ArgError},
Return,
};
use alloc::borrow::Cow; use alloc::borrow::Cow;
use bevy_utils::HashSet;
use thiserror::Error; use thiserror::Error;
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
@ -15,8 +20,14 @@ pub enum FunctionError {
#[error(transparent)] #[error(transparent)]
ArgError(#[from] ArgError), ArgError(#[from] ArgError),
/// The number of arguments provided does not match the expected number. /// The number of arguments provided does not match the expected number.
#[error("expected {expected} arguments but received {received}")] #[error("received {received} arguments but expected one of {expected:?}")]
ArgCountMismatch { expected: usize, received: usize }, ArgCountMismatch { expected: ArgCount, received: usize },
/// No overload was found for the given set of arguments.
#[error("no overload found for arguments with signature `{received:?}`, expected one of `{expected:?}`")]
NoOverload {
expected: HashSet<ArgumentSignature>,
received: ArgumentSignature,
},
} }
/// The result of calling a [`DynamicFunction`] or [`DynamicFunctionMut`]. /// The result of calling a [`DynamicFunction`] or [`DynamicFunctionMut`].
@ -28,6 +39,25 @@ pub enum FunctionError {
/// [`DynamicFunctionMut`]: crate::func::DynamicFunctionMut /// [`DynamicFunctionMut`]: crate::func::DynamicFunctionMut
pub type FunctionResult<'a> = Result<Return<'a>, FunctionError>; pub type FunctionResult<'a> = Result<Return<'a>, FunctionError>;
/// An error that occurs when attempting to add a function overload.
#[derive(Debug, Error, PartialEq)]
pub enum FunctionOverloadError {
/// A [`SignatureInfo`] was expected, but none was found.
///
/// [`SignatureInfo`]: crate::func::info::SignatureInfo
#[error("expected at least one `SignatureInfo` but found none")]
MissingSignature,
/// An error that occurs when attempting to add a function overload with a duplicate signature.
#[error("could not add function overload: duplicate found for signature `{0:?}`")]
DuplicateSignature(ArgumentSignature),
#[error(
"argument signature `{:?}` has too many arguments (max {})",
0,
ArgCount::MAX_COUNT
)]
TooManyArguments(ArgumentSignature),
}
/// An error that occurs when registering a function into a [`FunctionRegistry`]. /// An error that occurs when registering a function into a [`FunctionRegistry`].
/// ///
/// [`FunctionRegistry`]: crate::func::FunctionRegistry /// [`FunctionRegistry`]: crate::func::FunctionRegistry

View file

@ -1,5 +1,8 @@
use crate::{ use crate::{
func::{ArgList, DynamicFunction, FunctionInfo, FunctionResult}, func::{
args::{ArgCount, ArgList},
DynamicFunction, FunctionInfo, FunctionResult,
},
PartialReflect, PartialReflect,
}; };
use alloc::borrow::Cow; use alloc::borrow::Cow;
@ -45,12 +48,15 @@ pub trait Function: PartialReflect + Debug {
/// ///
/// [`DynamicFunctions`]: crate::func::DynamicFunction /// [`DynamicFunctions`]: crate::func::DynamicFunction
/// [`IntoFunction`]: crate::func::IntoFunction /// [`IntoFunction`]: crate::func::IntoFunction
fn name(&self) -> Option<&Cow<'static, str>> { fn name(&self) -> Option<&Cow<'static, str>>;
self.info().name()
}
/// The number of arguments this function accepts. /// Returns the number of arguments the function expects.
fn arg_count(&self) -> usize { ///
/// For [overloaded] functions that can have a variable number of arguments,
/// this will contain the full set of counts for all signatures.
///
/// [overloaded]: crate::func#overloading-functions
fn arg_count(&self) -> ArgCount {
self.info().arg_count() self.info().arg_count()
} }

View file

@ -1,62 +1,274 @@
use alloc::{borrow::Cow, vec}; use alloc::{borrow::Cow, vec};
use core::fmt::{Debug, Formatter};
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::{boxed::Box, format, vec}; use alloc::{boxed::Box, format, vec};
use variadics_please::all_tuples;
use crate::{ use crate::{
func::args::{ArgInfo, GetOwnership, Ownership}, func::args::{ArgCount, ArgCountOutOfBoundsError, ArgInfo, GetOwnership, Ownership},
func::signature::ArgumentSignature,
func::FunctionOverloadError,
type_info::impl_type_methods, type_info::impl_type_methods,
Type, TypePath, Type, TypePath,
}; };
use variadics_please::all_tuples;
/// Type information for a [`DynamicFunction`] or [`DynamicFunctionMut`]. /// Type information for a [`DynamicFunction`] or [`DynamicFunctionMut`].
/// ///
/// This information can be retrieved directly from certain functions and closures /// This information can be retrieved directly from certain functions and closures
/// using the [`TypedFunction`] trait, and manually constructed otherwise. /// using the [`TypedFunction`] trait, and manually constructed otherwise.
/// ///
/// It is compromised of one or more [`SignatureInfo`] structs,
/// allowing it to represent functions with multiple sets of arguments (i.e. "overloaded functions").
///
/// [`DynamicFunction`]: crate::func::DynamicFunction /// [`DynamicFunction`]: crate::func::DynamicFunction
/// [`DynamicFunctionMut`]: crate::func::DynamicFunctionMut /// [`DynamicFunctionMut`]: crate::func::DynamicFunctionMut
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FunctionInfo { pub struct FunctionInfo {
name: Option<Cow<'static, str>>, name: Option<Cow<'static, str>>,
args: Vec<ArgInfo>, arg_count: ArgCount,
return_info: ReturnInfo, signatures: Box<[SignatureInfo]>,
} }
impl FunctionInfo { impl FunctionInfo {
/// Create a new [`FunctionInfo`] for a function with the given name. /// Create a new [`FunctionInfo`] for a function with the given signature.
///
/// # Panics
///
/// Panics if the given signature has more than the maximum number of arguments
/// as specified by [`ArgCount::MAX_COUNT`].
pub fn new(signature: SignatureInfo) -> Self {
Self {
name: signature.name.clone(),
arg_count: ArgCount::new(signature.arg_count()).unwrap(),
signatures: vec![signature].into(),
}
}
/// Create a new [`FunctionInfo`] from a set of signatures.
///
/// Returns an error if the given iterator is empty or contains duplicate signatures.
pub fn try_from_iter(
signatures: impl IntoIterator<Item = SignatureInfo>,
) -> Result<Self, FunctionOverloadError> {
let mut iter = signatures.into_iter();
let base = iter.next().ok_or(FunctionOverloadError::MissingSignature)?;
if base.arg_count() > ArgCount::MAX_COUNT {
return Err(FunctionOverloadError::TooManyArguments(
ArgumentSignature::from(&base),
));
}
let mut info = Self::new(base);
for signature in iter {
if signature.arg_count() > ArgCount::MAX_COUNT {
return Err(FunctionOverloadError::TooManyArguments(
ArgumentSignature::from(&signature),
));
}
info = info.with_overload(signature).map_err(|sig| {
FunctionOverloadError::DuplicateSignature(ArgumentSignature::from(&sig))
})?;
}
Ok(info)
}
/// The base signature for this function.
///
/// All functions—including overloaded functions—are guaranteed to have at least one signature.
/// The first signature used to define the [`FunctionInfo`] is considered the base signature.
pub fn base(&self) -> &SignatureInfo {
&self.signatures[0]
}
/// Whether this function is overloaded.
///
/// This is determined by the existence of multiple signatures.
pub fn is_overloaded(&self) -> bool {
self.signatures.len() > 1
}
/// Set the name of the function.
pub fn with_name(mut self, name: Option<impl Into<Cow<'static, str>>>) -> Self {
self.name = name.map(Into::into);
self
}
/// The name of the function.
///
/// For [`DynamicFunctions`] created using [`IntoFunction`] or [`DynamicFunctionMuts`] created using [`IntoFunctionMut`],
/// the default name will always be the full path to the function as returned by [`std::any::type_name`],
/// unless the function is a closure, anonymous function, or function pointer,
/// in which case the name will be `None`.
///
/// For overloaded functions, this will be the name of the base signature,
/// unless manually overwritten using [`Self::with_name`].
///
/// [`DynamicFunctions`]: crate::func::DynamicFunction
/// [`IntoFunction`]: crate::func::IntoFunction
/// [`DynamicFunctionMuts`]: crate::func::DynamicFunctionMut
/// [`IntoFunctionMut`]: crate::func::IntoFunctionMut
pub fn name(&self) -> Option<&Cow<'static, str>> {
self.name.as_ref()
}
/// Add a signature to this function.
///
/// If a signature with the same [`ArgumentSignature`] already exists,
/// an error is returned with the given signature.
///
/// # Panics
///
/// Panics if the given signature has more than the maximum number of arguments
/// as specified by [`ArgCount::MAX_COUNT`].
pub fn with_overload(mut self, signature: SignatureInfo) -> Result<Self, SignatureInfo> {
let is_duplicate = self.signatures.iter().any(|s| {
s.arg_count() == signature.arg_count()
&& ArgumentSignature::from(s) == ArgumentSignature::from(&signature)
});
if is_duplicate {
return Err(signature);
}
self.arg_count.add(signature.arg_count());
self.signatures = IntoIterator::into_iter(self.signatures)
.chain(Some(signature))
.collect();
Ok(self)
}
/// Returns the number of arguments the function expects.
///
/// For [overloaded] functions that can have a variable number of arguments,
/// this will contain the full set of counts for all signatures.
///
/// [overloaded]: crate::func#overloading-functions
pub fn arg_count(&self) -> ArgCount {
self.arg_count
}
/// The signatures of the function.
///
/// This is guaranteed to always contain at least one signature.
/// Overloaded functions will contain two or more.
pub fn signatures(&self) -> &[SignatureInfo] {
&self.signatures
}
/// Returns a wrapper around this info that implements [`Debug`] for pretty-printing the function.
///
/// This can be useful for more readable debugging and logging.
///
/// # Example
///
/// ```
/// # use bevy_reflect::func::{FunctionInfo, TypedFunction};
/// #
/// fn add(a: i32, b: i32) -> i32 {
/// a + b
/// }
///
/// let info = add.get_function_info();
///
/// let pretty = info.pretty_printer();
/// assert_eq!(format!("{:?}", pretty), "(_: i32, _: i32) -> i32");
/// ```
pub fn pretty_printer(&self) -> PrettyPrintFunctionInfo {
PrettyPrintFunctionInfo::new(self)
}
/// Extend this [`FunctionInfo`] with another without checking for duplicates.
///
/// # Panics
///
/// Panics if the given signature has more than the maximum number of arguments
/// as specified by [`ArgCount::MAX_COUNT`].
pub(super) fn extend_unchecked(&mut self, other: FunctionInfo) {
if self.name.is_none() {
self.name = other.name;
}
let signatures = core::mem::take(&mut self.signatures);
self.signatures = IntoIterator::into_iter(signatures)
.chain(IntoIterator::into_iter(other.signatures))
.collect();
self.arg_count = self
.signatures
.iter()
.fold(ArgCount::default(), |mut count, sig| {
count.add(sig.arg_count());
count
});
}
}
impl TryFrom<SignatureInfo> for FunctionInfo {
type Error = ArgCountOutOfBoundsError;
fn try_from(signature: SignatureInfo) -> Result<Self, Self::Error> {
let count = signature.arg_count();
if count > ArgCount::MAX_COUNT {
return Err(ArgCountOutOfBoundsError(count));
}
Ok(Self::new(signature))
}
}
impl TryFrom<Vec<SignatureInfo>> for FunctionInfo {
type Error = FunctionOverloadError;
fn try_from(signatures: Vec<SignatureInfo>) -> Result<Self, Self::Error> {
Self::try_from_iter(signatures)
}
}
impl<const N: usize> TryFrom<[SignatureInfo; N]> for FunctionInfo {
type Error = FunctionOverloadError;
fn try_from(signatures: [SignatureInfo; N]) -> Result<Self, Self::Error> {
Self::try_from_iter(signatures)
}
}
#[derive(Debug, Clone)]
pub struct SignatureInfo {
name: Option<Cow<'static, str>>,
args: Box<[ArgInfo]>,
return_info: ReturnInfo,
}
impl SignatureInfo {
/// Create a new [`SignatureInfo`] for a function with the given name.
pub fn named(name: impl Into<Cow<'static, str>>) -> Self { pub fn named(name: impl Into<Cow<'static, str>>) -> Self {
Self { Self {
name: Some(name.into()), name: Some(name.into()),
args: Vec::new(), args: Box::new([]),
return_info: ReturnInfo::new::<()>(), return_info: ReturnInfo::new::<()>(),
} }
} }
/// Create a new [`FunctionInfo`] with no name. /// Create a new [`SignatureInfo`] with no name.
/// ///
/// For the purposes of debugging and [registration], /// For the purposes of debugging and [registration],
/// it's recommended to use [`FunctionInfo::named`] instead. /// it's recommended to use [`Self::named`] instead.
/// ///
/// [registration]: crate::func::FunctionRegistry /// [registration]: crate::func::FunctionRegistry
pub fn anonymous() -> Self { pub fn anonymous() -> Self {
Self { Self {
name: None, name: None,
args: Vec::new(), args: Box::new([]),
return_info: ReturnInfo::new::<()>(), return_info: ReturnInfo::new::<()>(),
} }
} }
/// Create a new [`FunctionInfo`] from the given function.
pub fn from<F, Marker>(function: &F) -> Self
where
F: TypedFunction<Marker>,
{
function.get_function_info()
}
/// Set the name of the function. /// Set the name of the function.
pub fn with_name(mut self, name: impl Into<Cow<'static, str>>) -> Self { pub fn with_name(mut self, name: impl Into<Cow<'static, str>>) -> Self {
self.name = Some(name.into()); self.name = Some(name.into());
@ -72,7 +284,9 @@ impl FunctionInfo {
name: impl Into<Cow<'static, str>>, name: impl Into<Cow<'static, str>>,
) -> Self { ) -> Self {
let index = self.args.len(); let index = self.args.len();
self.args.push(ArgInfo::new::<T>(index).with_name(name)); self.args = IntoIterator::into_iter(self.args)
.chain(Some(ArgInfo::new::<T>(index).with_name(name)))
.collect();
self self
} }
@ -83,7 +297,7 @@ impl FunctionInfo {
/// It's preferable to use [`Self::with_arg`] to add arguments to the function /// It's preferable to use [`Self::with_arg`] to add arguments to the function
/// as it will automatically set the index of the argument. /// as it will automatically set the index of the argument.
pub fn with_args(mut self, args: Vec<ArgInfo>) -> Self { pub fn with_args(mut self, args: Vec<ArgInfo>) -> Self {
self.args = args; self.args = IntoIterator::into_iter(self.args).chain(args).collect();
self self
} }
@ -167,6 +381,161 @@ impl ReturnInfo {
} }
} }
/// A wrapper around [`FunctionInfo`] that implements [`Debug`] for pretty-printing function information.
///
/// # Example
///
/// ```
/// # use bevy_reflect::func::{FunctionInfo, PrettyPrintFunctionInfo, TypedFunction};
/// #
/// fn add(a: i32, b: i32) -> i32 {
/// a + b
/// }
///
/// let info = add.get_function_info();
///
/// let pretty = PrettyPrintFunctionInfo::new(&info);
/// assert_eq!(format!("{:?}", pretty), "(_: i32, _: i32) -> i32");
/// ```
pub struct PrettyPrintFunctionInfo<'a> {
info: &'a FunctionInfo,
include_fn_token: bool,
include_name: bool,
}
impl<'a> PrettyPrintFunctionInfo<'a> {
/// Create a new pretty-printer for the given [`FunctionInfo`].
pub fn new(info: &'a FunctionInfo) -> Self {
Self {
info,
include_fn_token: false,
include_name: false,
}
}
/// Include the function name in the pretty-printed output.
pub fn include_name(mut self) -> Self {
self.include_name = true;
self
}
/// Include the `fn` token in the pretty-printed output.
pub fn include_fn_token(mut self) -> Self {
self.include_fn_token = true;
self
}
}
impl<'a> Debug for PrettyPrintFunctionInfo<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
if self.include_fn_token {
write!(f, "fn")?;
if self.include_name {
write!(f, " ")?;
}
}
match (self.include_name, self.info.name()) {
(true, Some(name)) => write!(f, "{}", name)?,
(true, None) => write!(f, "_")?,
_ => {}
}
if self.info.is_overloaded() {
// `{(arg0: i32, arg1: i32) -> (), (arg0: f32, arg1: f32) -> ()}`
let mut set = f.debug_set();
for signature in self.info.signatures() {
set.entry(&PrettyPrintSignatureInfo::new(signature));
}
set.finish()
} else {
// `(arg0: i32, arg1: i32) -> ()`
PrettyPrintSignatureInfo::new(self.info.base()).fmt(f)
}
}
}
/// A wrapper around [`SignatureInfo`] that implements [`Debug`] for pretty-printing function signature information.
///
/// # Example
///
/// ```
/// # use bevy_reflect::func::{FunctionInfo, PrettyPrintSignatureInfo, TypedFunction};
/// #
/// fn add(a: i32, b: i32) -> i32 {
/// a + b
/// }
///
/// let info = add.get_function_info();
///
/// let pretty = PrettyPrintSignatureInfo::new(info.base());
/// assert_eq!(format!("{:?}", pretty), "(_: i32, _: i32) -> i32");
/// ```
pub struct PrettyPrintSignatureInfo<'a> {
info: &'a SignatureInfo,
include_fn_token: bool,
include_name: bool,
}
impl<'a> PrettyPrintSignatureInfo<'a> {
/// Create a new pretty-printer for the given [`SignatureInfo`].
pub fn new(info: &'a SignatureInfo) -> Self {
Self {
info,
include_fn_token: false,
include_name: false,
}
}
/// Include the function name in the pretty-printed output.
pub fn include_name(mut self) -> Self {
self.include_name = true;
self
}
/// Include the `fn` token in the pretty-printed output.
pub fn include_fn_token(mut self) -> Self {
self.include_fn_token = true;
self
}
}
impl<'a> Debug for PrettyPrintSignatureInfo<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
if self.include_fn_token {
write!(f, "fn")?;
if self.include_name {
write!(f, " ")?;
}
}
match (self.include_name, self.info.name()) {
(true, Some(name)) => write!(f, "{}", name)?,
(true, None) => write!(f, "_")?,
_ => {}
}
write!(f, "(")?;
// We manually write the args instead of using `DebugTuple` to avoid trailing commas
// and (when used with `{:#?}`) unnecessary newlines
for (index, arg) in self.info.args().iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}
let name = arg.name().unwrap_or("_");
let ty = arg.type_path();
write!(f, "{name}: {ty}")?;
}
let ret = self.info.return_info().type_path();
write!(f, ") -> {ret}")
}
}
/// A static accessor to compile-time type information for functions. /// A static accessor to compile-time type information for functions.
/// ///
/// This is the equivalent of [`Typed`], but for function. /// This is the equivalent of [`Typed`], but for function.
@ -194,7 +563,7 @@ impl ReturnInfo {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// # use bevy_reflect::func::{ArgList, FunctionInfo, ReflectFnMut, TypedFunction}; /// # use bevy_reflect::func::{ArgList, ReflectFnMut, TypedFunction};
/// # /// #
/// fn print(value: String) { /// fn print(value: String) {
/// println!("{}", value); /// println!("{}", value);
@ -202,9 +571,9 @@ impl ReturnInfo {
/// ///
/// let info = print.get_function_info(); /// let info = print.get_function_info();
/// assert!(info.name().unwrap().ends_with("print")); /// assert!(info.name().unwrap().ends_with("print"));
/// assert_eq!(info.arg_count(), 1); /// assert!(info.arg_count().contains(1));
/// assert_eq!(info.args()[0].type_path(), "alloc::string::String"); /// assert_eq!(info.base().args()[0].type_path(), "alloc::string::String");
/// assert_eq!(info.return_info().type_path(), "()"); /// assert_eq!(info.base().return_info().type_path(), "()");
/// ``` /// ```
/// ///
/// # Trait Parameters /// # Trait Parameters
@ -243,6 +612,7 @@ macro_rules! impl_typed_function {
Function: FnMut($($Arg),*) -> ReturnType, Function: FnMut($($Arg),*) -> ReturnType,
{ {
fn function_info() -> FunctionInfo { fn function_info() -> FunctionInfo {
FunctionInfo::new(
create_info::<Function>() create_info::<Function>()
.with_args({ .with_args({
#[allow(unused_mut)] #[allow(unused_mut)]
@ -255,6 +625,7 @@ macro_rules! impl_typed_function {
] ]
}) })
.with_return_info(ReturnInfo::new::<ReturnType>()) .with_return_info(ReturnInfo::new::<ReturnType>())
)
} }
} }
@ -266,7 +637,8 @@ macro_rules! impl_typed_function {
for<'a> &'a ReturnType: TypePath + GetOwnership, for<'a> &'a ReturnType: TypePath + GetOwnership,
Function: for<'a> FnMut(&'a Receiver, $($Arg),*) -> &'a ReturnType, Function: for<'a> FnMut(&'a Receiver, $($Arg),*) -> &'a ReturnType,
{ {
fn function_info() -> $crate::func::FunctionInfo { fn function_info() -> FunctionInfo {
FunctionInfo::new(
create_info::<Function>() create_info::<Function>()
.with_args({ .with_args({
#[allow(unused_mut)] #[allow(unused_mut)]
@ -280,6 +652,7 @@ macro_rules! impl_typed_function {
] ]
}) })
.with_return_info(ReturnInfo::new::<&ReturnType>()) .with_return_info(ReturnInfo::new::<&ReturnType>())
)
} }
} }
@ -292,6 +665,7 @@ macro_rules! impl_typed_function {
Function: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a mut ReturnType, Function: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a mut ReturnType,
{ {
fn function_info() -> FunctionInfo { fn function_info() -> FunctionInfo {
FunctionInfo::new(
create_info::<Function>() create_info::<Function>()
.with_args({ .with_args({
#[allow(unused_mut)] #[allow(unused_mut)]
@ -305,6 +679,7 @@ macro_rules! impl_typed_function {
] ]
}) })
.with_return_info(ReturnInfo::new::<&mut ReturnType>()) .with_return_info(ReturnInfo::new::<&mut ReturnType>())
)
} }
} }
@ -317,6 +692,7 @@ macro_rules! impl_typed_function {
Function: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a ReturnType, Function: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a ReturnType,
{ {
fn function_info() -> FunctionInfo { fn function_info() -> FunctionInfo {
FunctionInfo::new(
create_info::<Function>() create_info::<Function>()
.with_args({ .with_args({
#[allow(unused_mut)] #[allow(unused_mut)]
@ -330,6 +706,7 @@ macro_rules! impl_typed_function {
] ]
}) })
.with_return_info(ReturnInfo::new::<&ReturnType>()) .with_return_info(ReturnInfo::new::<&ReturnType>())
)
} }
} }
}; };
@ -355,13 +732,13 @@ all_tuples!(impl_typed_function, 0, 15, Arg, arg);
/// | Function pointer | `fn() -> String` | `None` | /// | Function pointer | `fn() -> String` | `None` |
/// ///
/// [`type_name`]: core::any::type_name /// [`type_name`]: core::any::type_name
fn create_info<F>() -> FunctionInfo { fn create_info<F>() -> SignatureInfo {
let name = core::any::type_name::<F>(); let name = core::any::type_name::<F>();
if name.ends_with("{{closure}}") || name.starts_with("fn(") { if name.ends_with("{{closure}}") || name.starts_with("fn(") {
FunctionInfo::anonymous() SignatureInfo::anonymous()
} else { } else {
FunctionInfo::named(name) SignatureInfo::named(name)
} }
} }
@ -386,10 +763,10 @@ mod tests {
info.name().unwrap(), info.name().unwrap(),
"bevy_reflect::func::info::tests::should_create_function_info::add" "bevy_reflect::func::info::tests::should_create_function_info::add"
); );
assert_eq!(info.arg_count(), 2); assert_eq!(info.base().arg_count(), 2);
assert_eq!(info.args()[0].type_path(), "i32"); assert_eq!(info.base().args()[0].type_path(), "i32");
assert_eq!(info.args()[1].type_path(), "i32"); assert_eq!(info.base().args()[1].type_path(), "i32");
assert_eq!(info.return_info().type_path(), "i32"); assert_eq!(info.base().return_info().type_path(), "i32");
} }
#[test] #[test]
@ -405,10 +782,10 @@ mod tests {
let info = add.get_function_info(); let info = add.get_function_info();
assert!(info.name().is_none()); assert!(info.name().is_none());
assert_eq!(info.arg_count(), 2); assert_eq!(info.base().arg_count(), 2);
assert_eq!(info.args()[0].type_path(), "i32"); assert_eq!(info.base().args()[0].type_path(), "i32");
assert_eq!(info.args()[1].type_path(), "i32"); assert_eq!(info.base().args()[1].type_path(), "i32");
assert_eq!(info.return_info().type_path(), "i32"); assert_eq!(info.base().return_info().type_path(), "i32");
} }
#[test] #[test]
@ -423,10 +800,10 @@ mod tests {
let info = add.get_function_info(); let info = add.get_function_info();
assert!(info.name().is_none()); assert!(info.name().is_none());
assert_eq!(info.arg_count(), 2); assert_eq!(info.base().arg_count(), 2);
assert_eq!(info.args()[0].type_path(), "i32"); assert_eq!(info.base().args()[0].type_path(), "i32");
assert_eq!(info.args()[1].type_path(), "i32"); assert_eq!(info.base().args()[1].type_path(), "i32");
assert_eq!(info.return_info().type_path(), "i32"); assert_eq!(info.base().return_info().type_path(), "i32");
} }
#[test] #[test]
@ -442,9 +819,30 @@ mod tests {
let info = add.get_function_info(); let info = add.get_function_info();
assert!(info.name().is_none()); assert!(info.name().is_none());
assert_eq!(info.arg_count(), 2); assert_eq!(info.base().arg_count(), 2);
assert_eq!(info.args()[0].type_path(), "i32"); assert_eq!(info.base().args()[0].type_path(), "i32");
assert_eq!(info.args()[1].type_path(), "i32"); assert_eq!(info.base().args()[1].type_path(), "i32");
assert_eq!(info.return_info().type_path(), "()"); assert_eq!(info.base().return_info().type_path(), "()");
}
#[test]
fn should_pretty_print_info() {
// fn add(a: i32, b: i32) -> i32 {
// a + b
// }
//
// let info = add.get_function_info().with_name("add");
//
// let pretty = info.pretty_printer();
// assert_eq!(format!("{:?}", pretty), "(_: i32, _: i32) -> i32");
//
// let pretty = info.pretty_printer().include_fn_token();
// assert_eq!(format!("{:?}", pretty), "fn(_: i32, _: i32) -> i32");
//
// let pretty = info.pretty_printer().include_name();
// assert_eq!(format!("{:?}", pretty), "add(_: i32, _: i32) -> i32");
//
// let pretty = info.pretty_printer().include_fn_token().include_name();
// assert_eq!(format!("{:?}", pretty), "fn add(_: i32, _: i32) -> i32");
} }
} }

View file

@ -66,6 +66,6 @@ mod tests {
fn should_default_closure_name_to_none() { fn should_default_closure_name_to_none() {
let c = 23; let c = 23;
let func = (|a: i32, b: i32| a + b + c).into_function(); let func = (|a: i32, b: i32| a + b + c).into_function();
assert_eq!(func.info().name(), None); assert!(func.name().is_none());
} }
} }

View file

@ -81,6 +81,6 @@ mod tests {
fn should_default_closure_name_to_none() { fn should_default_closure_name_to_none() {
let mut total = 0; let mut total = 0;
let func = (|a: i32, b: i32| total = a + b).into_function_mut(); let func = (|a: i32, b: i32| total = a + b).into_function_mut();
assert_eq!(func.info().name(), None); assert!(func.name().is_none());
} }
} }

View file

@ -94,6 +94,32 @@
//! For other functions that don't conform to one of the above signatures, //! For other functions that don't conform to one of the above signatures,
//! [`DynamicFunction`] and [`DynamicFunctionMut`] can instead be created manually. //! [`DynamicFunction`] and [`DynamicFunctionMut`] can instead be created manually.
//! //!
//! # Generic Functions
//!
//! In Rust, generic functions are [monomophized] by the compiler,
//! which means that a separate copy of the function is generated for each concrete set of type parameters.
//!
//! When converting a generic function to a [`DynamicFunction`] or [`DynamicFunctionMut`],
//! the function must be manually monomorphized with concrete types.
//! In other words, you cannot write `add<T>.into_function()`.
//! Instead, you will need to write `add::<i32>.into_function()`.
//!
//! This means that reflected functions cannot be generic themselves.
//! To get around this limitation, you can consider [overloading] your function with multiple concrete types.
//!
//! # Overloading Functions
//!
//! Both [`DynamicFunction`] and [`DynamicFunctionMut`] support [function overloading].
//!
//! Function overloading allows one function to handle multiple types of arguments.
//! This is useful for simulating generic functions by having an overload for each known concrete type.
//! Additionally, it can also simulate [variadic functions]: functions that can be called with a variable number of arguments.
//!
//! Internally, this works by storing multiple functions in a map,
//! where each function is associated with a specific argument signature.
//!
//! To learn more, see the docs on [`DynamicFunction::with_overload`].
//!
//! # Function Registration //! # Function Registration
//! //!
//! This module also provides a [`FunctionRegistry`] that can be used to register functions and closures //! This module also provides a [`FunctionRegistry`] that can be used to register functions and closures
@ -127,6 +153,10 @@
//! [`Reflect`]: crate::Reflect //! [`Reflect`]: crate::Reflect
//! [lack of variadic generics]: https://poignardazur.github.io/2024/05/25/report-on-rustnl-variadics/ //! [lack of variadic generics]: https://poignardazur.github.io/2024/05/25/report-on-rustnl-variadics/
//! [coherence issues]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#coherence-leak-check //! [coherence issues]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#coherence-leak-check
//! [monomophized]: https://en.wikipedia.org/wiki/Monomorphization
//! [overloading]: #overloading-functions
//! [function overloading]: https://en.wikipedia.org/wiki/Function_overloading
//! [variadic functions]: https://en.wikipedia.org/wiki/Variadic_function
pub use args::{ArgError, ArgList, ArgValue}; pub use args::{ArgError, ArgList, ArgValue};
pub use dynamic_function::*; pub use dynamic_function::*;
@ -143,6 +173,7 @@ pub use return_type::*;
pub mod args; pub mod args;
mod dynamic_function; mod dynamic_function;
mod dynamic_function_internal;
mod dynamic_function_mut; mod dynamic_function_mut;
mod error; mod error;
mod function; mod function;
@ -154,18 +185,19 @@ mod reflect_fn;
mod reflect_fn_mut; mod reflect_fn_mut;
mod registry; mod registry;
mod return_type; mod return_type;
pub mod signature;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use alloc::borrow::Cow; use alloc::borrow::Cow;
use super::*;
use crate::func::args::ArgCount;
use crate::{ use crate::{
func::args::{ArgError, ArgList, Ownership}, func::args::{ArgError, ArgList, Ownership},
TypePath, TypePath,
}; };
use super::*;
#[test] #[test]
fn should_error_on_missing_args() { fn should_error_on_missing_args() {
fn foo(_: i32) {} fn foo(_: i32) {}
@ -176,7 +208,7 @@ mod tests {
assert_eq!( assert_eq!(
result.unwrap_err(), result.unwrap_err(),
FunctionError::ArgCountMismatch { FunctionError::ArgCountMismatch {
expected: 1, expected: ArgCount::new(1).unwrap(),
received: 0 received: 0
} }
); );
@ -192,7 +224,7 @@ mod tests {
assert_eq!( assert_eq!(
result.unwrap_err(), result.unwrap_err(),
FunctionError::ArgCountMismatch { FunctionError::ArgCountMismatch {
expected: 0, expected: ArgCount::new(0).unwrap(),
received: 1 received: 1
} }
); );

View file

@ -5,8 +5,9 @@ use alloc::{boxed::Box, format, vec};
use crate::{ use crate::{
func::{ func::{
args::FromArg, macros::count_tokens, ArgList, FunctionError, FunctionResult, IntoReturn, args::{ArgCount, FromArg},
ReflectFnMut, macros::count_tokens,
ArgList, FunctionError, FunctionResult, IntoReturn, ReflectFnMut,
}, },
Reflect, TypePath, Reflect, TypePath,
}; };
@ -96,7 +97,7 @@ macro_rules! impl_reflect_fn {
if args.len() != COUNT { if args.len() != COUNT {
return Err(FunctionError::ArgCountMismatch { return Err(FunctionError::ArgCountMismatch {
expected: COUNT, expected: ArgCount::new(COUNT).unwrap(),
received: args.len(), received: args.len(),
}); });
} }
@ -125,7 +126,7 @@ macro_rules! impl_reflect_fn {
if args.len() != COUNT { if args.len() != COUNT {
return Err(FunctionError::ArgCountMismatch { return Err(FunctionError::ArgCountMismatch {
expected: COUNT, expected: ArgCount::new(COUNT).unwrap(),
received: args.len(), received: args.len(),
}); });
} }
@ -155,7 +156,7 @@ macro_rules! impl_reflect_fn {
if args.len() != COUNT { if args.len() != COUNT {
return Err(FunctionError::ArgCountMismatch { return Err(FunctionError::ArgCountMismatch {
expected: COUNT, expected: ArgCount::new(COUNT).unwrap(),
received: args.len(), received: args.len(),
}); });
} }
@ -185,7 +186,7 @@ macro_rules! impl_reflect_fn {
if args.len() != COUNT { if args.len() != COUNT {
return Err(FunctionError::ArgCountMismatch { return Err(FunctionError::ArgCountMismatch {
expected: COUNT, expected: ArgCount::new(COUNT).unwrap(),
received: args.len(), received: args.len(),
}); });
} }

View file

@ -5,7 +5,9 @@ use alloc::{boxed::Box, format, vec};
use crate::{ use crate::{
func::{ func::{
args::FromArg, macros::count_tokens, ArgList, FunctionError, FunctionResult, IntoReturn, args::{ArgCount, FromArg},
macros::count_tokens,
ArgList, FunctionError, FunctionResult, IntoReturn,
}, },
Reflect, TypePath, Reflect, TypePath,
}; };
@ -102,7 +104,7 @@ macro_rules! impl_reflect_fn_mut {
if args.len() != COUNT { if args.len() != COUNT {
return Err(FunctionError::ArgCountMismatch { return Err(FunctionError::ArgCountMismatch {
expected: COUNT, expected: ArgCount::new(COUNT).unwrap(),
received: args.len(), received: args.len(),
}); });
} }
@ -131,7 +133,7 @@ macro_rules! impl_reflect_fn_mut {
if args.len() != COUNT { if args.len() != COUNT {
return Err(FunctionError::ArgCountMismatch { return Err(FunctionError::ArgCountMismatch {
expected: COUNT, expected: ArgCount::new(COUNT).unwrap(),
received: args.len(), received: args.len(),
}); });
} }
@ -161,7 +163,7 @@ macro_rules! impl_reflect_fn_mut {
if args.len() != COUNT { if args.len() != COUNT {
return Err(FunctionError::ArgCountMismatch { return Err(FunctionError::ArgCountMismatch {
expected: COUNT, expected: ArgCount::new(COUNT).unwrap(),
received: args.len(), received: args.len(),
}); });
} }
@ -191,7 +193,7 @@ macro_rules! impl_reflect_fn_mut {
if args.len() != COUNT { if args.len() != COUNT {
return Err(FunctionError::ArgCountMismatch { return Err(FunctionError::ArgCountMismatch {
expected: COUNT, expected: ArgCount::new(COUNT).unwrap(),
received: args.len(), received: args.len(),
}); });
} }

View file

@ -0,0 +1,234 @@
//! Function signature types.
//!
//! Function signatures differ from [`FunctionInfo`] and [`SignatureInfo`] in that they
//! are only concerned about the types and order of the arguments and return type of a function.
//!
//! The names of arguments do not matter,
//! nor does any other information about the function such as its name or other attributes.
//!
//! This makes signatures useful for comparing or hashing functions strictly based on their
//! arguments and return type.
//!
//! [`FunctionInfo`]: crate::func::info::FunctionInfo
use crate::func::args::ArgInfo;
use crate::func::{ArgList, SignatureInfo};
use crate::Type;
use bevy_utils::hashbrown::Equivalent;
use core::borrow::Borrow;
use core::fmt::{Debug, Formatter};
use core::hash::{Hash, Hasher};
use core::ops::{Deref, DerefMut};
/// The signature of a function.
///
/// This can be used as a way to compare or hash functions based on their arguments and return type.
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Signature {
args: ArgumentSignature,
ret: Type,
}
impl Signature {
/// Create a new function signature with the given argument signature and return type.
pub fn new(args: ArgumentSignature, ret: Type) -> Self {
Self { args, ret }
}
/// Get the argument signature of the function.
pub fn args(&self) -> &ArgumentSignature {
&self.args
}
/// Get the return type of the function.
pub fn return_type(&self) -> &Type {
&self.ret
}
}
impl Debug for Signature {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{:?} -> {:?}", self.args, self.ret)
}
}
impl<T: Borrow<SignatureInfo>> From<T> for Signature {
fn from(info: T) -> Self {
let info = info.borrow();
Self::new(ArgumentSignature::from(info), *info.return_info().ty())
}
}
/// A wrapper around a borrowed [`ArgList`] that can be used as an
/// [equivalent] of an [`ArgumentSignature`].
///
/// [equivalent]: Equivalent
pub(super) struct ArgListSignature<'a, 'b>(&'a ArgList<'b>);
impl Equivalent<ArgumentSignature> for ArgListSignature<'_, '_> {
fn equivalent(&self, key: &ArgumentSignature) -> bool {
self.len() == key.len() && self.iter().eq(key.iter())
}
}
impl<'a, 'b> ArgListSignature<'a, 'b> {
pub fn iter(&self) -> impl ExactSizeIterator<Item = &Type> {
self.0.iter().map(|arg| {
arg.value()
.get_represented_type_info()
.unwrap_or_else(|| {
panic!("no `TypeInfo` found for argument: {:?}", arg);
})
.ty()
})
}
pub fn len(&self) -> usize {
self.0.len()
}
}
impl Eq for ArgListSignature<'_, '_> {}
impl PartialEq for ArgListSignature<'_, '_> {
fn eq(&self, other: &Self) -> bool {
self.len() == other.len() && self.iter().eq(other.iter())
}
}
impl Hash for ArgListSignature<'_, '_> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.iter().for_each(|arg| {
arg.value()
.get_represented_type_info()
.unwrap_or_else(|| {
panic!("no `TypeInfo` found for argument: {:?}", arg);
})
.ty()
.hash(state);
});
}
}
impl<'a, 'b> From<&'a ArgList<'b>> for ArgListSignature<'a, 'b> {
fn from(args: &'a ArgList<'b>) -> Self {
Self(args)
}
}
/// The argument-portion of a function signature.
///
/// For example, given a function signature `(a: i32, b: f32) -> u32`,
/// the argument signature would be `(i32, f32)`.
///
/// This can be used as a way to compare or hash functions based on their arguments.
#[derive(Clone)]
pub struct ArgumentSignature(Box<[Type]>);
impl Debug for ArgumentSignature {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let mut tuple = f.debug_tuple("");
for ty in self.0.iter() {
tuple.field(ty);
}
tuple.finish()
}
}
impl Deref for ArgumentSignature {
type Target = [Type];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for ArgumentSignature {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Eq for ArgumentSignature {}
impl PartialEq for ArgumentSignature {
fn eq(&self, other: &Self) -> bool {
self.0.len() == other.0.len() && self.0.iter().eq(other.0.iter())
}
}
impl Hash for ArgumentSignature {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.iter().for_each(|ty| ty.hash(state));
}
}
impl FromIterator<Type> for ArgumentSignature {
fn from_iter<T: IntoIterator<Item = Type>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
impl<T: Borrow<SignatureInfo>> From<T> for ArgumentSignature {
fn from(info: T) -> Self {
Self(
info.borrow()
.args()
.iter()
.map(ArgInfo::ty)
.copied()
.collect(),
)
}
}
impl From<&ArgList<'_>> for ArgumentSignature {
fn from(args: &ArgList) -> Self {
Self(
args.iter()
.map(|arg| {
arg.value()
.get_represented_type_info()
.unwrap_or_else(|| {
panic!("no `TypeInfo` found for argument: {:?}", arg);
})
.ty()
})
.copied()
.collect(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::func::TypedFunction;
#[test]
fn should_generate_signature_from_function_info() {
fn add(a: i32, b: f32) -> u32 {
(a as f32 + b).round() as u32
}
let info = add.get_function_info();
let signature = Signature::from(info.base());
assert_eq!(signature.args().0.len(), 2);
assert_eq!(signature.args().0[0], Type::of::<i32>());
assert_eq!(signature.args().0[1], Type::of::<f32>());
assert_eq!(*signature.return_type(), Type::of::<u32>());
}
#[test]
fn should_debug_signature() {
let signature = Signature::new(
ArgumentSignature::from_iter(vec![Type::of::<&mut String>(), Type::of::<i32>()]),
Type::of::<()>(),
);
assert_eq!(
format!("{:?}", signature),
"(&mut alloc::string::String, i32) -> ()"
);
}
}

View file

@ -8,8 +8,8 @@
use bevy::reflect::{ use bevy::reflect::{
func::{ func::{
ArgList, DynamicFunction, DynamicFunctionMut, FunctionInfo, FunctionResult, IntoFunction, ArgList, DynamicFunction, DynamicFunctionMut, FunctionResult, IntoFunction,
IntoFunctionMut, Return, IntoFunctionMut, Return, SignatureInfo,
}, },
PartialReflect, Reflect, PartialReflect, Reflect,
}; };
@ -83,7 +83,50 @@ fn main() {
dbg!(closure.call_once(args).unwrap()); dbg!(closure.call_once(args).unwrap());
assert_eq!(count, 5); assert_eq!(count, 5);
// As stated before, this works for many kinds of simple functions. // Generic functions can also be converted into a `DynamicFunction`,
// however, they will need to be manually monomorphized first.
fn stringify<T: ToString>(value: T) -> String {
value.to_string()
}
// We have to manually specify the concrete generic type we want to use.
let function = stringify::<i32>.into_function();
let args = ArgList::new().push_owned(123_i32);
let return_value = function.call(args).unwrap();
let value: Box<dyn PartialReflect> = return_value.unwrap_owned();
assert_eq!(value.try_take::<String>().unwrap(), "123");
// To make things a little easier, we can also "overload" functions.
// This makes it so that a single `DynamicFunction` can represent multiple functions,
// and the correct one is chosen based on the types of the arguments.
// Each function overload must have a unique argument signature.
let function = stringify::<i32>
.into_function()
.with_overload(stringify::<f32>);
// Now our `function` accepts both `i32` and `f32` arguments.
let args = ArgList::new().push_owned(1.23_f32);
let return_value = function.call(args).unwrap();
let value: Box<dyn PartialReflect> = return_value.unwrap_owned();
assert_eq!(value.try_take::<String>().unwrap(), "1.23");
// Function overloading even allows us to have a variable number of arguments.
let function = (|| 0)
.into_function()
.with_overload(|a: i32| a)
.with_overload(|a: i32, b: i32| a + b)
.with_overload(|a: i32, b: i32, c: i32| a + b + c);
let args = ArgList::new()
.push_owned(1_i32)
.push_owned(2_i32)
.push_owned(3_i32);
let return_value = function.call(args).unwrap();
let value: Box<dyn PartialReflect> = return_value.unwrap_owned();
assert_eq!(value.try_take::<i32>().unwrap(), 6);
// As stated earlier, `IntoFunction` works for many kinds of simple functions.
// Functions with non-reflectable arguments or return values may not be able to be converted. // Functions with non-reflectable arguments or return values may not be able to be converted.
// Generic functions are also not supported (unless manually monomorphized like `foo::<i32>.into_function()`). // Generic functions are also not supported (unless manually monomorphized like `foo::<i32>.into_function()`).
// Additionally, the lifetime of the return value is tied to the lifetime of the first argument. // Additionally, the lifetime of the return value is tied to the lifetime of the first argument.
@ -118,7 +161,7 @@ fn main() {
let value: &dyn PartialReflect = return_value.unwrap_ref(); let value: &dyn PartialReflect = return_value.unwrap_ref();
assert_eq!(value.try_downcast_ref::<String>().unwrap(), "Hello, world!"); assert_eq!(value.try_downcast_ref::<String>().unwrap(), "Hello, world!");
// Lastly, for more complex use cases, you can always create a custom `DynamicFunction` manually. // For more complex use cases, you can always create a custom `DynamicFunction` manually.
// This is useful for functions that can't be converted via the `IntoFunction` trait. // This is useful for functions that can't be converted via the `IntoFunction` trait.
// For example, this function doesn't implement `IntoFunction` due to the fact that // For example, this function doesn't implement `IntoFunction` due to the fact that
// the lifetime of the return value is not tied to the lifetime of the first argument. // the lifetime of the return value is not tied to the lifetime of the first argument.
@ -150,7 +193,7 @@ fn main() {
// This makes it easier to debug and is also required for function registration. // This makes it easier to debug and is also required for function registration.
// We can either give it a custom name or use the function's type name as // We can either give it a custom name or use the function's type name as
// derived from `std::any::type_name_of_val`. // derived from `std::any::type_name_of_val`.
FunctionInfo::named(std::any::type_name_of_val(&get_or_insert)) SignatureInfo::named(std::any::type_name_of_val(&get_or_insert))
// We can always change the name if needed. // We can always change the name if needed.
// It's a good idea to also ensure that the name is unique, // It's a good idea to also ensure that the name is unique,
// such as by using its type name or by prefixing it with your crate name. // such as by using its type name or by prefixing it with your crate name.