-
Notifications
You must be signed in to change notification settings - Fork 210
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
Proc-macro interface for async-await export #975
Conversation
Converted the remaining macro_rules! used by the `#[methods]` macro into procedural macros, increasing flexibility for future expansion and reducing reliance on `#[doc(hidden)]` macro items. This makes it easier to support more combinations of method signatures, without having to manually write out n^2 patterns. A shim for the legacy `godot_wrap_method` macro is kept for compatibility. It can be removed in the next breaking release.
bors try |
tryBuild failed: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just realized I didn't submit my review I started a few days ago. Not yet exhaustive, just a first few thoughts. Thanks a lot for this great addition! 🙂
/// Convenience macro to wrap an object's method into a `Method` implementor | ||
/// that can be passed to the engine when registering a class. | ||
#[proc_macro] | ||
#[deprecated = "The legacy manual export macro is deprecated and will be removed in a future godot-rust version. \ | ||
Either use the `#[methods]` attribute macro, or implement the `Method` trait manually instead."] | ||
pub fn godot_wrap_method(input: TokenStream) -> TokenStream { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clever! I think for all practical purposes, turning a declarative macro into a procedural function-like one should not constitute a breaking change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It shouldn't. There used to be a limitation where procedural function-like macros couldn't be used in expressions, but that was lifted quite a while ago (below our MSRV).
gdnative-derive/src/lib.rs
Outdated
/// Returns the (possibly renamed or imported as `gdnative`) identifier of the `gdnative_core` crate. | ||
fn crate_gdnative_core() -> proc_macro2::TokenStream { | ||
let found_crate = proc_macro_crate::crate_name("gdnative-core") | ||
.or_else(|_| proc_macro_crate::crate_name("gdnative")) | ||
.expect("crate not found"); | ||
|
||
match found_crate { | ||
proc_macro_crate::FoundCrate::Itself => quote!(crate), | ||
proc_macro_crate::FoundCrate::Name(name) => { | ||
let ident = proc_macro2::Ident::new(&name, proc_macro2::Span::call_site()); | ||
quote!( #ident ) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a really nice change, hardcoding the crate name was something that bothered me a bit in the past.
/// // This will NOT compile: Rust assumes that any futures returned by an `async fn` may only live as long as each of its | ||
/// // arguments, and there is no way to tell it otherwise. As a result, it will emit some cryptic complaints about lifetime. | ||
/// #[method] | ||
/// async fn answer(&self) -> i32 { | ||
/// 42 | ||
/// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this not contradicting the code examples further above, where you have async fn
s that are apparently? Or is the difference the lack of return value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically it isn't, but I see how it can be confusing. As far as the macro is concerned, this declaration is valid. The only thing preventing it from compiling is Rust's lifetime elision rules, and those rules can be relaxed down the line such that code like this compile.
I'll try to think of a way to word the examples better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I think it would help to have a rule for users to know when they can use async fn
and when not.
Maybe some of these rules could also be detected by the proc-macro itself, to give a better compiler message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a problem with macro-based detection though: we still can't emit proper warnings. I don't think we want async fn (&self)
to be a hard error here, for the reasons above, but any warnings we emit are still through #[deprecated]
, and as such a user cannot #[allow]
them precisely without also allowing all the actually deprecated stuff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-worded the examples.
gdnative-derive/src/lib.rs
Outdated
/// - `#[ctx]` - The [async context](gdnative::tasks::Context), for async methods. See the `async` argument | ||
/// below. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typical parameter declaration is
#[ctx] ctx: Arc<Context>
Is that clear enough? Or should the type perhaps be named AsyncContext
? The latter would make sure that it doesn't make sense in a non-async function signature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renaming the type would constitute a breaking change though, and I'm not exactly sure how much it's going to matter: currently the user have to import the Context
type from gdnative::tasks::Context
, which already indicates that it's related to async. The API is a dead giveaway too. I guess the attribute can be renamed #[async_ctx]
if the extra clarity is worth the verbosity.
Note that the macro emits a rather informative error (imo) when it sees a #[ctx]
in a non-async function:
gdnative/gdnative/tests/ui/derive_fail_methods_special_args.stderr
Lines 55 to 59 in ef25b42
error: the async context is only available to async methods | |
--> tests/ui/derive_fail_methods_special_args.rs:20:26 | |
| | |
20 | fn sync(self, #[ctx] ctx: ()) {} | |
| ^^^ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah true, this type was already there. #[async_ctx]
was actually my first idea, but I thought in the type it might be clearer.
#[async_ctx]
might be a bit clearer, but maybe we should ask some users. On the other hand, long signatures might be broken into multiple lines by rustfmt, and then having the same length as #[opt]
would be a tiny readability bonus 😉probably not that much difference in practice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to #[async_ctx]
for now.
Thanks for the review! I'll implement most of the suggested changes tomorrow. |
ef25b42
to
425cc3b
Compare
Added support for exporting `async fn`s and `fn`s that return futures through the proc-macro interface. - Added `#[method(async)]`, for methods that return futures but aren't async themselves. This is especially useful for working around lifetime elision (Rust putting the lifetime of `&self` into the return values of `async fn`s). - Added the `#[ctx]` attribute that denotes the async context. Special arguments (`#[base]`, `#[ctx]`, and possibly `#[self]` in the future) can now be declared in any order, so long as they precede all regular arguments. - Methods without receivers can now be exported. However, they still currently use `Map` semantics. Async exports are unsupported by the legacy `godot_wrap_method` shim.
425cc3b
to
6689a00
Compare
bors try |
tryBuild succeeded: |
Will merge a day later so related development can continue. |
bors r+ |
Build succeeded: |
This is a two-part PR that accomplishes the following:
Fully proceduralize method exports
Converted the remaining macro_rules! used by the
#[methods]
macro into procedural macros, increasing flexibility for future expansion and reducing reliance on#[doc(hidden)]
macro items.This makes it easier to support more combinations of method signatures, without having to manually write out n^2 patterns.
Proc-macro interface for async-await export
Added support for exporting
async fn
s andfn
s that return futures through the proc-macro interface.#[method(async)]
, for methods that return futures but aren't async themselves. This is especially useful for working around lifetime elision (Rust putting the lifetime of&self
into the return values ofasync fn
s).#[ctx]
attribute that denotes the async context. Special arguments (#[base]
,#[ctx]
, and possibly#[self]
in the future) can now be declared in any order, so long as they precede all regular arguments.Map
semantics.Utterly double close #284.
Error messages
As a side effect to this refactoring, the macros are now capable of generating some fairly precisely spanned error messages:
derive_fail_methods_special_args.stderr
Close #439.
Compatibility
There aren't any breaking changes that I'm aware of. A shim for the legacy
godot_wrap_method
macro is kept for compatibility. It can be removed in the next breaking release. Async exports are unsupported by this shim.