Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-organize type-level attributes in NativeClass derive macro #848

Open
Tracked by #767
chitoyuu opened this issue Jan 9, 2022 · 6 comments
Open
Tracked by #767

Re-organize type-level attributes in NativeClass derive macro #848

chitoyuu opened this issue Jan 9, 2022 · 6 comments
Labels
breaking-change Issues and PRs that are breaking to fix/merge. c: export Component: export (mod export, derive) quality-of-life No new functionality, but improves ergonomics/internals
Milestone

Comments

@chitoyuu
Copy link
Contributor

chitoyuu commented Jan 9, 2022

The NativeClass macro currently utilizes a large number of type-level attributes, making it tricky to add further expansions to its functionality. The current attributes are:

  • inherit
  • user_data
  • register_with
  • no_constructor

... while in the future, we might also like to add:

Proposal

My proposal is as follows:

  • inherit is to be left as a top-level attribute, because of its significance.
  • All other attributes are to be bundled into a single #[nativescript], as key-value pairs when applicable: #[nativescript(user_data = "path::to::Type<Self>", register_with = "path::to::function", no_constructor)]

Under this new arrangement, generic bounds and static renames can easily be introduced as keys under the #[nativescript] attribute: #[nativescript(bound = "T: SomeBound", rename = "SomeNewName")]. Prior art for this could be found in the ecosystem, for example, in Serde.

Unresolved questions

It's a common complaint that the signature of the mandatory new is too verbose, the owner argument being seldom used. However, it isn't obvious how this is best dealt with. Our problem here is the need to distinguish three different semantics at the syntax level:

  • Calling a function, possibly a custom one, with no arguments.
  • Calling a function, possibly a custom one, with a single owner argument.
  • Having no default constructor.

I can see two alternatives to the current situation:

Changing the default constructor to the nullary Default::default, requiring a constructor key for unary constructors

Under this arrangement, the possible annotations are:

  • Nothing. Default::default is called to produce a new instance by default.
  • #[nativescript(constructor = "Self::new")]. Self::new is called with the owner argument to produce a new instance. This is consistent with the current behavior.
  • #[nativescript(no_constructor)]. The NativeScript type would have no default constructor.
  • All other cases would become compile errors.

This makes it much less verbose to declare types with constructors that do not use the owner argument, and frees up the new identifier unless requested specifically by the user. The downside is that extra annotation is required to reproduce the current default.

Replacing no_constructor with a nested key-value pair under the constructor meta

Under this arrangement, the possible annotations are:

  • #[nativescript(constructor(nullary = "Self::default"))]. Self::default is called with no arguments to produce a new instance.
  • #[nativescript(constructor(unary = "Self::new"))]. Self::new is called with the owner argument to produce a new instance.
  • #[nativescript(constructor(none))]. The NativeScript type would have no default constructor.
  • All other cases would become compile errors.

This has the benefit of allowing custom functions for both the nullary case and the unary case, but is very verbose. It should be noted that Default can always be implemented for types with nullary constructors, even though it could be undesirable if the constructor in question is expensive, or if there are multiple such functions.

@chitoyuu chitoyuu added the quality-of-life No new functionality, but improves ergonomics/internals label Jan 9, 2022
@chitoyuu
Copy link
Contributor Author

chitoyuu commented Jan 9, 2022

Should be considered for #842 if #603 is desirable in 0.10.

@Bromeon Bromeon added the c: export Component: export (mod export, derive) label Jan 9, 2022
@Bromeon
Copy link
Member

Bromeon commented Jan 9, 2022

Thank you for the detailed proposal! 🙂


All other attributes are to be bundled into a single #[nativescript] [...]

Since we have #[derive(NativeClass)], wouldn't it be more natural if the attribute were #[nativeclass] or #[native_class] instead of #[nativescript]?

Maybe in the future with GDExtensions, we could even consider something more general. Just brainstorming, e.g. #[derive(GodotClass)] and #[godot(...)].


Changing the default constructor to the nullary Default::default, requiring a constructor key for unary constructors

I like this idea. We could benefit of the rusty "default constructor idiom".

I think one limitation is that the derive-macro for NativeClass already needs to know how its constructor looks, no? Because otherwise, an alternative might be something like:

#[methods]
impl MyClass {
    #[constructor]
    fn new() -> Self { ... }

    // or:

    #[constructor(owner)]
    fn new(owner: &Node) -> Self { ... }
}

@Bromeon Bromeon added this to the v0.10.0 milestone Jan 9, 2022
@Bromeon Bromeon added the breaking-change Issues and PRs that are breaking to fix/merge. label Jan 9, 2022
@chitoyuu
Copy link
Contributor Author

chitoyuu commented Jan 9, 2022

Maybe in the future with GDExtensions, we could even consider something more general. Just brainstorming, e.g. #[derive(GodotClass)] and #[godot(...)].

It's my opinion that we should instead keep NativeScript and GDExtension classes distinct on the name level, since their behavior differ in many ways, e.g. godotengine/godot#54999. An easy s/NativeClass/GdExtension/g migration might not be possible even if all the external APIs are kept exactly the same, and as such I don't think we should make our identifiers imply that.

I think one limitation is that the derive-macro for NativeClass already needs to know how its constructor looks, no?

Currently yes. I think it can be moved to NativeClassMethods instead, but NativeClassMethods as a whole is just a kludge around the lack of global discovery (#350) -- ideally we want to be able to have an arbitrary number of #[methods] blocks, and don't want that trait to exist at all. In that case, it would become a run-time error to have multiple constructors declared for a single type.

That would have to be left for 0.11 anyway, though, since #350 isn't in the scope for the 0.10 milestone, so I think it would also be acceptable to move it into NativeClassMethods for now.

@Bromeon
Copy link
Member

Bromeon commented Jan 9, 2022

It's my opinion that we should instead keep NativeScript and GDExtension classes distinct on the name level, since their behavior differ in many ways [...]

I see, makes sense.

What about #[nativeclass] or #[native_class], when we have #[derive(NativeClass)]? I.e. instead of #[nativescript].

I'm pretty sure most godot-rust users can't tell the difference between the two terms, so differing here might add more confusion than benefit. Especially since there's also a rather unrelated NativeScript class.

That would have to be left for 0.11 anyway, though, since #350 isn't in the scope for the 0.10 milestone, so I think it would also be acceptable to move it into NativeClassMethods for now.

Okay, let's revisit this later then. There are quite a few pending improvements related to exporting, so some of them could be bundled for v0.11.

@chitoyuu
Copy link
Contributor Author

chitoyuu commented Jan 9, 2022

What about #[nativeclass] or #[native_class], when we have #[derive(NativeClass)]? I.e. instead of #[nativescript].

I suppose #[native_class] is fine, if we aren't renaming NativeClass to something else. You are right that renaming it to NativeScript outright would be confusing. Sometimes I forget that the Godot NativeScript class existed.

bors bot added a commit that referenced this issue Jan 10, 2022
847: Separate static name from `NativeClass` into new trait r=Bromeon a=chitoyuu

This allows NativeScript types to be registered under names only determined
at run time, enabling better ergonomics for libraries.

Ref #601, #603

## Unresolved questions

This does not implement the `#[name = "MyCustomName"]` static renaming syntax as suggested in #601 (comment), because it's very uncommon (if at all possible) for a key-value pair to be the top-level meta in an attribute. It should also be noted that at this moment, the `NativeClass` macro already utilizes a large number of type-level attributes:

- `inherit`
- `user_data`
- `register_with`
- `no_constructor`

...so it might not be wise to add another into the mix, especially under such a non-specific identifier as `name`. We might want to consider bundling (some of) these into a single top-level attribute (like `variant` for the `From`/`ToVariant` macros) instead. See #848.

Co-authored-by: Chitose Yuuzaki <chitoyuu@potatoes.gay>
@Bromeon
Copy link
Member

Bromeon commented Feb 6, 2022

Moving to 0.11 milestone, to avoid blocking the 0.10 release, along with #601.
I plan to have the next minor release not so far in the future, so it's less of a "now or only in 1 year" problem.

@Bromeon Bromeon modified the milestones: v0.10.0, v0.11 Feb 6, 2022
@chitoyuu chitoyuu modified the milestones: unplanned, v0.12.0, v0.13.0 Nov 30, 2022
bors bot added a commit that referenced this issue Jan 4, 2023
999: Implement automatic `NativeClass` registration via inventory. Implement mix-ins. r=chitoyuu a=chitoyuu

## Implement automatic NativeClass registration via inventory

This adds the optional `inventory` feature, which allows `NativeClass` types to be automatically registered on supported platforms (everything that OSS Godot currently supports except WASM).

Run-time diagnostic functions are added to help debug missing registration problems that are highly likely to arise when porting `inventory`-enabled projects to WASM.

An internal `cfg_ex` attribute macro is added to help manage cfg conditions.

Close #350. Note that the standalone registration attribute syntax proposed by the original issue isn't implemented, for the limited usefulness -- there are much fewer cases where manual `NativeClass` impls are necessary thanks to all the improvements since the original issue.

## Implement mix-in `#[methods]` blocks

Adds the `#[methods(mixin = "Name")]` syntax for declaring mix-in blocks. Mix-in blocks have a many-to-many relationship with `NativeClass` types. Both `impl Type` and `impl Trait for Type` blocks are accepted.

The argument name is changed from `as` in the original proposal to `mixin`, because we might still want to keep universal `#[methods]` blocks in the future for ease of use with WASM. `#[methods(mixin)]` makes a lot more sense for a future auto-registered mixin block than `#[methods(as /* as what? */)]`.

All mix-in blocks have to be manually registered for gdnative v0.11.x. Some difficulty was encountered when trying to make auto-mixins compatible with existing code. It might still be possible with some tricks like autoref-specialization, but that might be too much effort given that we likely want to re-design much of the hierarchy for 0.12.

Close #984.

## Allow `#[register_with]` for `#[monomorphize]`

Enables `#[monomorphize]` to take the same standalone `#[register_with]` attribute as `#[derive(NativeClass)]`. This is chosen for short term consistency, but will probably change in a later version w/ #848, which might not still get implemented for a fair bit of time.


Co-authored-by: Chitose Yuuzaki <chitoyuu@potatoes.gay>
bors bot added a commit that referenced this issue Jan 4, 2023
999: Implement automatic `NativeClass` registration via inventory. Implement mix-ins. r=chitoyuu a=chitoyuu

## Implement automatic NativeClass registration via inventory

This adds the optional `inventory` feature, which allows `NativeClass` types to be automatically registered on supported platforms (everything that OSS Godot currently supports except WASM).

Run-time diagnostic functions are added to help debug missing registration problems that are highly likely to arise when porting `inventory`-enabled projects to WASM.

An internal `cfg_ex` attribute macro is added to help manage cfg conditions.

Close #350. Note that the standalone registration attribute syntax proposed by the original issue isn't implemented, for the limited usefulness -- there are much fewer cases where manual `NativeClass` impls are necessary thanks to all the improvements since the original issue.

## Implement mix-in `#[methods]` blocks

Adds the `#[methods(mixin = "Name")]` syntax for declaring mix-in blocks. Mix-in blocks have a many-to-many relationship with `NativeClass` types. Both `impl Type` and `impl Trait for Type` blocks are accepted.

The argument name is changed from `as` in the original proposal to `mixin`, because we might still want to keep universal `#[methods]` blocks in the future for ease of use with WASM. `#[methods(mixin)]` makes a lot more sense for a future auto-registered mixin block than `#[methods(as /* as what? */)]`.

All mix-in blocks have to be manually registered for gdnative v0.11.x. Some difficulty was encountered when trying to make auto-mixins compatible with existing code. It might still be possible with some tricks like autoref-specialization, but that might be too much effort given that we likely want to re-design much of the hierarchy for 0.12.

Close #984.

## Allow `#[register_with]` for `#[monomorphize]`

Enables `#[monomorphize]` to take the same standalone `#[register_with]` attribute as `#[derive(NativeClass)]`. This is chosen for short term consistency, but will probably change in a later version w/ #848, which might not still get implemented for a fair bit of time.


Co-authored-by: Chitose Yuuzaki <chitoyuu@potatoes.gay>
bors bot added a commit that referenced this issue Jan 4, 2023
999: Implement automatic `NativeClass` registration via inventory. Implement mix-ins. r=chitoyuu a=chitoyuu

## Implement automatic NativeClass registration via inventory

This adds the optional `inventory` feature, which allows `NativeClass` types to be automatically registered on supported platforms (everything that OSS Godot currently supports except WASM).

Run-time diagnostic functions are added to help debug missing registration problems that are highly likely to arise when porting `inventory`-enabled projects to WASM.

An internal `cfg_ex` attribute macro is added to help manage cfg conditions.

Close #350. Note that the standalone registration attribute syntax proposed by the original issue isn't implemented, for the limited usefulness -- there are much fewer cases where manual `NativeClass` impls are necessary thanks to all the improvements since the original issue.

## Implement mix-in `#[methods]` blocks

Adds the `#[methods(mixin = "Name")]` syntax for declaring mix-in blocks. Mix-in blocks have a many-to-many relationship with `NativeClass` types. Both `impl Type` and `impl Trait for Type` blocks are accepted.

The argument name is changed from `as` in the original proposal to `mixin`, because we might still want to keep universal `#[methods]` blocks in the future for ease of use with WASM. `#[methods(mixin)]` makes a lot more sense for a future auto-registered mixin block than `#[methods(as /* as what? */)]`.

All mix-in blocks have to be manually registered for gdnative v0.11.x. Some difficulty was encountered when trying to make auto-mixins compatible with existing code. It might still be possible with some tricks like autoref-specialization, but that might be too much effort given that we likely want to re-design much of the hierarchy for 0.12.

Close #984.

## Allow `#[register_with]` for `#[monomorphize]`

Enables `#[monomorphize]` to take the same standalone `#[register_with]` attribute as `#[derive(NativeClass)]`. This is chosen for short term consistency, but will probably change in a later version w/ #848, which might not still get implemented for a fair bit of time.


Co-authored-by: Chitose Yuuzaki <chitoyuu@potatoes.gay>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking-change Issues and PRs that are breaking to fix/merge. c: export Component: export (mod export, derive) quality-of-life No new functionality, but improves ergonomics/internals
Projects
None yet
Development

No branches or pull requests

2 participants