# `#[reflect_impl]` This attribute can be attached to an impl block to easily generate a structure containing the `DynamicFunction` equivalent of items within that block. ## Definitions - **Impl Block** - The implementation definition of a type. These included standard impl blocks (e.g. `impl MyType {/* ... */}`) and trait impl blocks (e.g. `impl MyTrait for MyType {/* ... */}`). There may be zero or more impl blocks for any given type. - **Impl Item** - Any method, associated function, associated constant, associated type, or macro invocation within an impl block. For the purposes of `#[reflect_impl]`, the latter two are ignored. These may also be referred to as "members" or simply "items" depending on the context. - **Method** - A function within an impl block that takes some form of `self` as the receiver (e.g. `&self`, `&mut self`, `self: Box<Self>`, etc.). Such functions are callable as `my_type.my_method()`. - **Associated Function** - A function within an impl block that is merely associated with the type. These do not take `self` and must be called as `MyType::my_function()`. - **Associated Constant** - A constant within an impl block. These constants become associated with the type and only accessible as `MyType::CONSTANT`. - **Associated Type** - A type alias defined within an impl block—specifically within trait impl blocks where the trait requires that the implementor define the type. - **Signature** - A function signature. This normally includes the function's name, its ordered set of argument types, and the return type. - **Argument Signature** - The argument portion of a function signature. This is the ordered set of argument types. - **Tracked Item** - An item that has been opted into being tracked by this macro via a `#[reflect]` attribute, allowing it to be accessible via reflection. - **Monomorphization** - In the context of function reflection, this simply means converting generic type parameters to concrete ones. This is done so that the compiler knows what types to use in place of the generics. For example, the function `fn foo<T>() {/* ... */}` would have to monomorphized down to `foo::<u32>` in order to be reflected. ## Supported Items This attribute currently only supports the following impl items: - Methods - Associated Functions - Associated Constants - Associated Types All tracked associated constants must be reflectable. Additionally, all arguments and return types for tracked associated functions and methods must also be reflectable. All tracked associated types must have a `'static` lifetime. The `impl Trait` type is supported in argument position using the [`as`](#as) attribute. For return types, `impl Trait` only works if given the necessary bounds for return type function reflection (currently `Reflect`, `TypePath`, `GetOwnership`, and `IntoReturn`). ### Generics There are two kinds of generics that this macro is aware of: impl generics and item generics. #### Impl Generics Impl generics are the generics that are defined on the impl definition: ```rust impl<'a, T: Debug> MyGenericType<'a, T> { // ... } ``` Impl generics don't need to be monomorphized for the macro. That job is left to the consumer of the generated struct. However, generics that are used by one of the tracked items may require additional reflection-only bounds. These bounds do not affect the impl block directly, but instead are used in the generated trait implementations to ensure compatability with reflection. See the [`where`](#where) attribute for details. #### Item Generics Item generics, on the other hand, are generics defined on the individual items: ```rust impl SomeTrait for MyType { type Foo<T> = Vec<T>; fn bar<T: Debug>() { // ... } fn baz<'a, const N: usize>(&self, value: N) { // ... } } ``` Due to limitations in Rust, these kinds of generics need to be monomorphized. This is done with the [`as`](#as) attribute. #### Lifetimes Lifetimes are effectively ignored in almost all cases. The only time lifetimes tend to really matter is when the return type of an associated function or method is a reference type. In such cases, its lifetime must be tied to the first argument in the function's signature (most often `&self`/`&mut self`). It's also important to remember that reflection requires a `'static` lifetime. For example: ```rust fn foo(&self, new_value: Foo) {/* ... */} ``` In the code above, `&self` implies `Self: 'static` and allows any lifetime for the reference (i.e. `&`), and `Foo` also implies `Foo: 'static`. Again, this is due to the fact that `Reflect` requires `'static`. ## Syntax All `#[reflect_impl]` attributes require exactly one argument: the identifier of the generated struct. For example, `#[reflect_impl(FooImpl)]` would generate a struct called `FooImpl` in the same scope as the impl block. ## Helper Attributes This macro comes with a set of quasi-helper attributes. These are attributes that are used to configure the generated output or behavior of the macro itself. All helper attributes are defined with `#[reflect]` as the base attribute. ### Block Attributes #### `vis` - **Syntax:** `vis = VISIBILITY` - **Default:** *None (private)* - **Optionality:** *Optional* This attribute controls the visibility of the generated struct. For example, to make the generated struct publicly accessible, you would write `#[reflect(vis = pub)]`. #### `doc` - **Syntax:** `doc = STRING_LITERAL | BOOLEAN` - **Default:** *None* - **Optionality:** *Optional* With the `documentation` feature enabled, impl blocks will store their doc comments on the generated struct. This attribute can be used to override that behavior by providing a custom doc string. ```rust /// Regular doc comment, not picked up by reflection /// thanks to the custom `doc` attribute. #[reflect(doc = "Custom reflection-only doc comment!")] impl Foo { // ... } ``` You can also completely opt-out of documentation for the block by specifying `doc = false`. ```rust /// Regular doc comment, not picked up by reflection /// thanks to the custom `doc` attribute. #[reflect(doc = false)] impl Foo { // ... } ``` #### `where` - **Syntax:** `where WHERE_CLAUSE_PREDICATE` - **Default:** *None* - **Optionality:** *Required for generic impls that need to reflect their generics* Generic types that are used within tracked items may need additional bounds in order to be reflectable. Most often, they will need to either be `Reflect` or `FromReflect`. In such cases, it's required that those bounds be added manually for the generated output. This attribute allows that to be done without having to modify the impl block itself. ```rust #[reflect(where T: Reflect, U: FromReflect)] impl<T, U> MyType<T, U> { #[reflect] fn foo() -> T {/* ... */} #[reflect] fn bar() -> Vec<U> {/* ... */} } ``` ### Item Attributes By default, items that are not tagged with a `#[reflect]` attribute will not be included in the generated output. This makes the feature purely opt-in, allowing reflectable items to coexist in the same impl block as non-reflectable items. #### `name` - **Syntax:** `name = STRING_LITERAL` - **Default:** *The fully qualified path to the item* - **Optionality:** *Optional* - **Applicability:** *Method*, *Associated Function*, *Associated Constant* This attribute defines the name of the `DynamicFunction` associated with this item. For example the following method will generate a `DynamicFunction` with the name `"get"`. ```rust #[reflect(name = "get")] fn value(&self) -> f32 { self.value } ``` By contrast, items that do not define a name will be named by their fully-qualified path as returned by `std::any::type_name_of_val`. In the example above, this might look something like `"my_crate::MyType::value"`. If uniqueness is desired, it's recommended to not define a custom name for your item. #### `doc` - **Syntax:** `doc = STRING_LITERAL | BOOLEAN` - **Default:** *None* - **Optionality:** *Optional* - **Applicability:** *Method*, *Associated Function*, *Associated Constant* With the `documentation` feature enabled, items will keep store their doc comments on their associated `DynamicFunction`. This attribute can be used to override that behavior by providing a custom doc string. ```rust /// Regular doc comment, not picked up by reflection /// thanks to the custom `doc` attribute. #[reflect(doc = "Custom reflection-only doc comment!")] fn value(&self) -> f32 { self.value } ``` You can also completely opt-out of documentation for an item by specifying `doc = false`. ```rust /// Regular doc comment, not picked up by reflection /// thanks to the custom `doc` attribute. #[reflect(doc = false)] fn value(&self) -> f32 { self.value } ``` #### `as` - **Syntax:** `as FUNCTION` | `as FN_POINTER` - **Default:** *None* - **Optionality:** *Required for generic items and items with `impl Trait` arguments* - **Applicability:** *Method*, *Associated Function* Generic items and `impl Trait` arguments need to be manually monomorphized in order to be reflected. This means that the concrete types must be provided manually by the attribute. For items that do not contain `impl Trait` arguments, you can do this by specifying the generics of you function: ```rust #[reflect(as Self::parse_string::<i32, f32>)] fn parse_string<In, Out>(&mut self, input: In, value: String, options: &ParseOptions) -> Out { // ... } ``` If the item does contain `impl Trait` arguments, then you must instead provide a valid function pointer to define the monomorphization of your item: ```rust #[reflect(as fn(&mut Self, i32, String, &ParseOptions) -> f32] fn parse_with<In, Out>(&mut self, input: In, value: impl ToString, options: &ParseOptions) -> Out { // ... } ``` As a tip, you can optionally replace non-generic types with `_` to allow the compiler to infer them since they will always be the same type. ```rust #[reflect(as fn(_, i32, String, _) -> f32] fn parse_with<In, Out>(&mut self, input: In, value: impl ToString, options: &ParseOptions) -> Out { // ... } ``` Additionally, you can provide argument names to improve readability: ```rust #[reflect(as fn(_, input: i32, value: String, _) -> f32] fn parse_with<In, Out>(&mut self, input: In, value: impl ToString, options: &ParseOptions) -> Out { // ... } ``` Also note that when coercing to a function pointer, the default name of the item becomes `None` (since function pointers are considered anonymous) and can't be registered. Therefore, it's recommended to also ensure the item has a custom name: ```rust fn #[reflect(name = "parse_with", as fn(_, input: i32, value: String, _) -> f32] fn parse_with<In, Out>(&mut self, input: In, value: impl ToString, options: &ParseOptions) -> Out { // ... } ``` This attribute can be paired with the [`variant`](#variant) and [`overload_with`](#overload_with) attributes to define multiple monomorphizations or overloads of an item. #### `overload_with` - **Syntax:** `overload_with(FUNC_0, ...FUNCTION_N )` where `FUNC` can either be `FN_POINTER` or `FUNCTION [as FN_POINTER]` - **Default:** *None* - **Optionality:** *Optional* - **Applicability:** *Method*, *Associated Function* This attribute allows a method or associated function to be overloaded with one or more items of the same kind. Any provided function must must have a different argument signature than the original item and all other provided overloads. Duplicate argument signatures cannot be detected at compile-time and will instead result in a runtime error so it is important to ensure the argument signatures all differ. If the item is generic or contains `impl Trait` arguments, a valid monomorphization must be provided. ```rust #[reflect( as fn(u32) -> bool, overload_with( // Referencing another item: Self::convert_empty, // Using monomorphized function pointers for this item: fn(u8) -> bool, fn(u16) -> bool, // Using a fully-qualified monomorphization of this item: Self::convert::<u64, bool>, // Referencing another item that needs to be monomorphized: Self::convert_string as fn(String) -> bool, // Note: uncommenting this line would cause a runtime error // since its argument signature matches `Self::convert_empty`: // Self::convert::<(), ()> ) )] fn convert<In, Out>(input: In) -> Out { // ... } fn convert_string<Out>(input: impl ToString) -> Out { // ... } fn convert_empty(input: ()) {} ``` #### `variant` - **Syntax:** `variant(name = STRING_LITERAL, ...ITEM_ATTRIBUTES)` - **Default:** *None* - **Optionality:** *Optional* - **Applicability:** *Method*, *Associated Function*, *Associated Constant* This attribute allows multiple versions of a single item to be created. Any other item attribute defined within this one will be passed on to the associated `DynamicFunction`. When there are two or more instances of this attribute on an item, a [`name`](#name) attribute must be defined within each. Additionally, any attributes defined outside this attribute will automatically be defaulted to if not provided within the `variant`. ```rust #[reflect(doc = "This doc comment is used by `foo` and `bar`.")] #[reflect(variant(name = "foo"))] #[reflect(variant(name = "bar"))] #[reflect(variant(name = "baz", doc = "Custom `baz` doc comment!"))] fn value(&self) -> f32 { self.value } ``` This is attribute is commonly used with the [`as`](#as) attribute to define mutliple instances of a generic item. #### Custom Attributes - **Syntax:** `@TYPE` - **Default:** *None* - **Optionality:** *Optional* - **Applicability:** *Method*, *Associated Function*, *Associated Constant* Custom attributes can be assigned to certain items similar to how the `Reflect` derive macro handles it. Custom attributes are essentially just types that can be accessed via the item's type information (e.g. `FunctionInfo`). ```rust #[reflect(@RangeInclusive::<i32>::new(0, 100))] fn set(&mut self, value: i32) { // ... } ``` ## Example ### Input Impl ```rust= /// My impl block! #[reflect_impl(MyTypeImpl)] #[reflect(where T: Reflect)] impl<T: Debug> MyType<T> { #[reflect] const CONSTANT: bool = true; /// Create a new instance of `MyType`. #[reflect] fn new() -> Self {/* ... */} #[reflect(doc = "Returns the value.")] #[reflect(variant(name = "get"))] #[reflect(variant(name = "value"))] fn value(&self) -> &T {/* ... */} #[reflect(name = "set")] fn set_value(&mut self, value: T) {/* ... */} fn is_set(&self) -> bool {/* ... */} #[reflect(variant(name = "contains_u32", as fn(_, u32) -> _))] #[reflect(variant(name = "contains_i32", as fn(_, i32) -> _))] #[reflect(variant( name = "contains_signed", as fn(_, i8) -> bool, overload_with( fn(_, i16) -> bool, fn(_, i32) -> bool, fn(_, i64) -> bool, fn(_, i128) -> bool, ) ))] #[reflect(variant( name = "contains_unsigned", as Self::contains::<u8>, overload_with( Self::contains::<u16>, Self::contains::<u32>, Self::contains::<u64>, Self::contains::<u128>, ) ))] fn contains<U>(&self, value: U) -> bool {/* ... */} #[reflect(overload_with(Self::increment_by))] fn increment(&mut self) {/* ... */} fn increment_by(&mut self, size: usize) {/* ... */} #[reflect(as fn(_, i32) -> [_; N])] fn print<const N: usize>(&self, value: impl Debug) -> [String; N] {/* ... */} } ``` ### Output Struct The following sections contain samples of what the generated output *might* look like. Types and bounds have been removed and/or simplified for brevity. #### Output Struct Definition ```rust= struct MyTypeImpl<T> { CONSTANT: DynamicFunction<'static>, new: DynamicFunction<'static>, value: [DynamicFunction<'static>; 2], set_value: DynamicFunction<'static>, contains: [DynamicFunction<'static>; 4], increment: DynamicFunction<'static>, print: DynamicFunction<'static>, _phantom: PhantomData<fn() -> (T,)>, } ``` #### Output Struct Impl ```rust= impl<T: Debug> MyTypeImpl<T> where T: Reflect, { pub fn CONSTANT(&self) -> &DynamicFunction<'static> { &self.CONSTANT } pub fn new(&self) -> &DynamicFunction<'static> { &self.new } pub fn value(&self, variant: &str) -> Result<&DynamicFunction<'static>, UnknownFunctionVariant> { match variant { "get" => Ok(&self.value[0]), "value" => Ok(&self.value[1]), _ => Err(UnknownFunctionVariant(err.to_string())) } } pub fn set_value(&self) -> &DynamicFunction<'static> { &self.set_value } pub fn contains(&self, variant: &str) -> Result<&DynamicFunction<'static>, UnknownFunctionVariant> { match variant { "contains_u32" => Ok(&self.contains[0]), "contains_i32" => Ok(&self.contains[1]), "contains_signed" => Ok(&self.contains[2]), "contains_unsigned" => Ok(&self.contains[3]), _ => Err(UnknownFunctionVariant(err.to_string())) } } pub fn increment(&self) -> &DynamicFunction<'static> { &self.increment } pub fn print(&self) -> &DynamicFunction<'static> { &self.print } } ``` #### Output Struct `Default` Impl ```rust= impl<T: Debug> Default for MyTypeImpl<T> where T: Reflect, { fn default() -> Self { CONSTANT: (|| <MyType<T>>::CONSTANT).into_function(), new: <MyType<T>>::new.into_function() .with_doc("Create a new instance of `MyType`."), value: [ <MyType<T>>::value.into_function() .with_name("get") .with_doc("Returns the value."), <MyType<T>>::value.into_function() .with_name("value") .with_doc("Returns the value."), ], set_value: <MyType<T>>::set_value.into_function() .with_name("set"), contains: [ (<MyType<T>>::contains as fn(_, u32) -> _).into_function() .with_name("contains_u32"), (<MyType<T>>::contains as fn(_, i32) -> _).into_function() .with_name("contains_i32"), (<MyType<T>>::contains as fn(_, i8) -> _).into_function() .with_overload((<MyType<T>>::contains as fn(_, i16) -> _)) .with_overload((<MyType<T>>::contains as fn(_, i32) -> _)) .with_overload((<MyType<T>>::contains as fn(_, i64) -> _)) .with_overload((<MyType<T>>::contains as fn(_, i128) -> _)) .with_name("contains_signed"), <MyType<T>>::contains::<u8>.into_function() .with_overload(<MyType<T>>::contains::<u16>) .with_overload(<MyType<T>>::contains::<u32>) .with_overload(<MyType<T>>::contains::<u64>) .with_overload(<MyType<T>>::contains::<u128>) .with_name("contains_unsigned"),, ], increment: <MyType<T>>::increment.into_function() .with_overload(<MyType<T>>::increment_by), print: (<MyType<T>>::print as fn(_, i32) -> [_; N]).into_function(), _phantom: Default::default(), } } ``` #### Output Struct `RegisterFunctions` Impl ```rust= impl<T: Debug> RegisterFunctions for MyTypeImpl<T> where T: Reflect, { fn register_functions(registry: &mut FunctionRegistry) -> Result<(), FunctionRegistrationError> { let this = Self::default(); registry.register(this.CONSTANT)?; registry.register(this.new)? { let [value_0, value_1] = this.value; registry.register(value_0)?; registry.register(value_1)?; } registry.register(this.set_value)? { let [contains_0, contains_1, contains_2, contains_3] = this.contains; registry.register(contains_0)?; registry.register(contains_1)?; registry.register(contains_2)?; registry.register(contains_3)?; } registry.register(this.increment)?; // Not possible without custom name: // registry.register(this.print)?; Ok(()) } } ``` #### Output Struct `Impl` Impl ```rust= impl<T: Debug> Impl for MyTypeImpl<T> where T: Reflect, { type Type = MyType<T>; // Note that `print` is inaccessible by name since // it was cast to a function pointer and not given // an explicit name fn get(&self, name: &str) -> Option<&DynamicFunction<'static>> { match name { // === Custom Names === // "get" => Some(&self.value[0]), "value" => Some(&self.value[1]), "set" => Some(&self.set_value), "contains_u32" => Some(&self.contains[0]), "contains_i32" => Some(&self.contains[1]), "contains_signed" => Some(&self.contains[2]), "contains_unsigned" => Some(&self.contains[3]), // === Default Short Names (requires namespacing) === // // "new" => Some(&self.new), // "increment" => Some(&self.increment), // "print" => Some(&self.print), // "CONSTANT" => Some(&self.constant), // === Default Long Names === // name if name == std::any::type_name_of_val(&Self::Type::new) => Some(&self.new), name if name == std::any::type_name_of_val(&Self::Type::increment) => Some(&self.increment), name if match_const(std::any::type_name::<Self::Type>(), "CONSTANT", name) => Some(&self.CONSTANT), // === Default === // _ => None } } fn assoc_constants(&self) -> impl Iterator<Item=&DynamicFunction<'static>> { [&self.CONSTANT].into_iter() } fn assoc_functions(&self) -> impl Iterator<Item=&DynamicFunction<'static>> { [&self.new].into_iter() } fn methods(&self) -> impl Iterator<Item=&DynamicFunction<'static>> { [ &self.value[0], &self.value[1], &self.set_value, &self.contains[0], &self.contains[1], &self.contains[2], &self.contains[3], &self.increment, &self.print, ].into_iter() } fn documentation(&self) -> Option<Cow<'static, str>> { Some(Cow::Borrowed("My impl block!")) } } ``` ## Open Questions ### Bikeshedding Obviously, there's a lot to bikeshed. I don't think we should get hung up on naming too much—these can always be changed—but there's also no harm in giving some space to share opinions. ### Formatted Naming Right now we only accept string literals. Should we include syntax for customizing the string? For example, `name = "{Self}::foo"` might generate a name like `"my_crate::MyType::<u32>::foo"`. Or maybe `name("{}::foo", std::any::type_name::<Self>())` to give even more control? The contents would just be pasted verbatim inside a `format!`. ### Other Attributes Not every attribute needs to be part of the initial MVP. But it's good to think about as many as we might want to add so that we can set ourselves up for when we do eventually get to that point. If there are any attributes we should add (or any we should remove), feel free to share! ## Future Work ### Automatic Type Association This macro should be completely usable on its own. It generates a struct that provides all the immediate functionality needed by users. Eventually, though, we'd like to associate the generated impl struct on the actual type. For example: ```rust #[derive(Reflect)] #[reflect(with_impls(MyTypeImpl))] struct MyType<T> {/* ... */} ``` This would have two effects. Firstly, it would enable users to register all impls for a type just like they can with type data. We would just need to implement the `RegisterFunctions` trait on the type as well. Secondly, it would allow us to add methods to `Struct`, `TupleStruct`, etc. for accessing these impl items. Additionally, if we ever add automatic registration, we can likely do this all automatically (possibly without even needing to generate a separate impl struct). ### Namespacing A big feature that has yet to be added to function reflection is namespacing. How we handle creating, managing, and registering namespaces will likely alter the behavior of this attribute. For example, rather than using `type_name` as the default name, we might instead opt to use the namespaced name as the default. Such decisions can be made once namespacing is figured out, but it's important we have the thought in the back of our minds as we move forward with this feature. ### Remote Reflection We can also make this attribute work for remote types by simply consuming the impl block. This allows a user to copy+paste the impl block from the third-party's crate and automatically generate an impl struct for it. This can be used to reflect impls that aren't reflected or override already-reflected impls with your own. Of course, this requires that the items themselves are reflectable. This won't be part of the initial MVP, but is something to be mindful of when working on this macro. Namely, we want to avoid backing ourselves into a corner with regards to the orphan rule. So we should ensure all generated output can easily work with external types/traits as well.