-
-
Notifications
You must be signed in to change notification settings - Fork 216
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
Support associated types in #[godot_dyn]
#1022
Support associated types in #[godot_dyn]
#1022
Conversation
fdd5527
to
80062ff
Compare
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-1022 |
1 similar comment
This comment was marked as duplicate.
This comment was marked as duplicate.
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.
Thanks a lot, this was very quick 😲
In the future, it might also happen that such constraints are extended to associated consts (rust-lang/rust#92827), but I don't think this is happening soon. If someone uses nightly Rust and needs this, they would be welcome to submit another PR 🙂
godot-macros/src/class/godot_dyn.rs
Outdated
let associated_impl = if associated_types.is_empty() { | ||
None | ||
} else { | ||
Some(quote! { <#(#associated_types, )*>}) | ||
}; |
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.
"Impl" is a bit misleading, as there's no impl
block involved.
Also, unless we need to distinguish Some
/None
in some logic, godot-rust typically uses the empty token stream.
let associated_impl = if associated_types.is_empty() { | |
None | |
} else { | |
Some(quote! { <#(#associated_types, )*>}) | |
}; | |
let assoc_type_constraints = if associated_types.is_empty() { | |
TokenStream::new() | |
} else { | |
quote! { < #(#associated_types),* > } | |
}; |
Please apply suggestion verbatim (also spacing + ,
changed).
#[godot_dyn]
it seems that I'll pretty much double the tests in |
Why? Can't you just add another trait with an associated type, and implement it for one type with |
Hmm, you are right – I can reduce amount of tests, since I haven't spot any weird side effects 🤔 – I'll leave two implementors and reduce amount of test cases. EDIT: done. I removed |
e716815
to
8b71e60
Compare
Rebased on current master, resolved conflicts |
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.
Implementation looks good, but tests decrease coverage slightly and are a bit harder to understand now. I made some suggestions on how to simplify things, while still testing the full associated type mechanics.
node.free(); | ||
} | ||
|
||
#[itest] | ||
fn dyn_gd_downcast() { | ||
let original = Gd::from_object(RefcHealth { hp: 20 }).into_dyn(); | ||
let original: DynGd<RefcHealth, dyn Health> = Gd::from_object(RefcHealth { hp: 20 }).into_dyn(); |
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.
All these tests also checked type inference: when there's only one candidate of a trait, the compiler is smart enough to figure it out. Explicitly specifying types defeats this.
Would this still be necessary with my IdProvider
proposal below? Would be cool if you could revert changes to existing tests regarding type inference; these should keep running as-is.
trait HealthWithAssociatedType { | ||
type HealthType; | ||
|
||
fn get_report(&self) -> Self::HealthType; | ||
|
||
fn set_report(&mut self, new: Self::HealthType); | ||
} |
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.
Maybe you can instead extend the InstanceIdProvider
, e.g. like this?
trait IdProvider {
type Id;
fn get_id_dynamic(&self) -> Self::Id;
}
That would require less changes and keep the two traits easier to distinguish.
(Also, if you move it to its original declaration location, the diff is easier to inspect).
8bc6057
to
3b3b3c6
Compare
It is worth noting that type inference doesn't work – and results in very confusing errors – in most cases (sic!) if there are multiple possible In this example foreign::NodeHealth is implementor of two dyn_gd traits: fn dyn_gd_creation_deref() {
let obj = foreign::NodeHealth::new_alloc();
let original_id = obj.instance_id();
let mut obj = obj.into_dyn();
let dyn_id = obj.instance_id();
assert_eq!(dyn_id, original_id);
deal_20_damage(&mut *obj.dyn_bind_mut());
assert_eq!(obj.dyn_bind().get_hitpoints(), 80);
obj.free();
}
fn deal_20_damage(h: &mut Health) {
h.deal_damage(20);
}
trait Health {
(…)
} Results in bunch of very confusing errors: Checking itest v0.0.0 (/home/irwin/apps/godot/opensource-contr/missing_docs/gdext/itest/rust)
error[E0597]: `obj` does not live long enough
--> itest/rust/src/object_tests/dyn_gd_test.rs:68:26
|
63 | let mut obj = obj.into_dyn();
| ------- binding `obj` declared here
...
68 | deal_20_damage(&mut *obj.dyn_bind_mut());
| ^^^---------------
| |
| borrowed value does not live long enough
| argument requires that `obj` is borrowed for `'static`
...
72 | }
| - `obj` dropped here while still borrowed
error[E0716]: temporary value dropped while borrowed
--> itest/rust/src/object_tests/dyn_gd_test.rs:68:26
|
68 | deal_20_damage(&mut *obj.dyn_bind_mut());
| ---------------------^^^^^^^^^^^^^^^^^^-- temporary value is freed at the end of this statement
| | |
| | creates a temporary value which is freed while still in use
| argument requires that borrow lasts for `'static`
error[E0502]: cannot borrow `obj` as immutable because it is also borrowed as mutable
--> itest/rust/src/object_tests/dyn_gd_test.rs:69:16
|
68 | deal_20_damage(&mut *obj.dyn_bind_mut());
| ------------------
| |
| mutable borrow occurs here
| argument requires that `obj` is borrowed for `'static`
69 | assert_eq!(obj.dyn_bind().get_hitpoints(), 80);
| ^^^ immutable borrow occurs here
error[E0505]: cannot move out of `obj` because it is borrowed
--> itest/rust/src/object_tests/dyn_gd_test.rs:71:5
|
63 | let mut obj = obj.into_dyn();
| ------- binding `obj` declared here
...
68 | deal_20_damage(&mut *obj.dyn_bind_mut());
| ------------------
| |
| borrow of `obj` occurs here
| argument requires that `obj` is borrowed for `'static`
...
71 | obj.free();
| ^^^ move out of `obj` occurs here It might be that This issue should be mentioned and documented in the book. The errors are unhelpful and confusing. It is next to impossible to know what is going on without knowledge about an inner implementation (which might require some digging – Current solutions to this problem:
let mut obj = obj.into_dyn::<dyn Health>();
let mut obj: DynGd<_, dyn Health> = obj.into_dyn();.
trait Health: 'static {
…
}
|
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.
Good points. I moved them to their own issue, since they have nothing to do with associated type.
Small things left, then this should be ready! Please also squash commits once ready from your side 🙂
|
||
let mut node = node.into_dyn::<dyn Health>(); | ||
// type can be safely inferred. |
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.
// type can be safely inferred. | |
// Type can be safely inferred. |
let obj = foreign::NodeHealth::new_alloc(); | ||
let original_id = obj.instance_id(); | ||
|
||
// trait must be explicitly declared if multiple AsDyn<...> trait implementations exists. |
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.
// trait must be explicitly declared if multiple AsDyn<...> trait implementations exists. | |
// `dyn Health` must be explicitly declared if multiple AsDyn<...> trait implementations exist. |
- Add support for traits with associated types for #[godot_dyn]`. - Refactor tests to account for both cases.
3b3b3c6
to
72ac6d4
Compare
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.
Thanks a lot!
closes #1021.
WIP - adds support for associated types for
#[godot_dyn]
macro. Requires some more extensive unit testing - I'm not sure if there are any possible side effects.(also my macros aren't great, feel free to critique them)