Skip to content

Commit e4c44de

Browse files
authored
Merge pull request #1308 from godot-rust/feature/as-object-arg-unify
Merge `AsObjectArg<T>` into `AsArg<Gd<T>>`
2 parents 07c34e7 + 9df02cf commit e4c44de

File tree

16 files changed

+236
-347
lines changed

16 files changed

+236
-347
lines changed

godot-codegen/src/conv/type_conversions.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,7 @@ fn to_rust_type_uncached(full_ty: &GodotTy, ctx: &mut Context) -> RustTy {
237237

238238
RustTy::EngineClass {
239239
tokens: quote! { Gd<#qualified_class> },
240-
object_arg: quote! { ObjectArg<#qualified_class> },
241-
impl_as_object_arg: quote! { impl AsObjectArg<#qualified_class> },
240+
impl_as_object_arg: quote! { impl AsArg<Option<Gd<#qualified_class>>> },
242241
inner_class: ty,
243242
}
244243
}

godot-codegen/src/generator/functions_common.rs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ pub(crate) enum FnArgExpr {
392392
/// How parameters are declared in a function signature.
393393
#[derive(Copy, Clone)]
394394
pub(crate) enum FnParamDecl {
395-
/// Public-facing, i.e. `T`, `&T`, `impl AsArg<T>` or `impl AsObjectArg<T>`.
395+
/// Public-facing, i.e. `T`, `&T`, `impl AsArg<T>`.
396396
FnPublic,
397397

398398
/// Public-facing with explicit lifetime, e.g. `&'a T`. Used in `Ex` builder methods.
@@ -401,7 +401,7 @@ pub(crate) enum FnParamDecl {
401401
/// Parameters in internal methods, used for delegation.
402402
FnInternal,
403403

404-
/// Store in a field, i.e. `v`, `CowArg<T>` or `ObjectCow<T>`.
404+
/// Store in a field, i.e. `v` or `CowArg<T>`.
405405
Field,
406406
}
407407

@@ -446,20 +446,24 @@ pub(crate) fn make_param_or_field_type(
446446
let mut special_ty = None;
447447

448448
let param_ty = match ty {
449-
// Objects: impl AsObjectArg<T>
449+
// Objects: impl AsArg<Gd<T>>
450450
RustTy::EngineClass {
451-
object_arg,
452451
impl_as_object_arg,
453452
inner_class,
454453
..
455454
} => {
456-
special_ty = Some(quote! { #object_arg });
455+
let lft = lifetimes.next();
456+
special_ty = Some(quote! { CowArg<#lft, Option<Gd<crate::classes::#inner_class>>> });
457457

458458
match decl {
459459
FnParamDecl::FnPublic => quote! { #impl_as_object_arg },
460-
FnParamDecl::FnPublicLifetime => quote! { #impl_as_object_arg },
461-
FnParamDecl::FnInternal => quote! { #object_arg },
462-
FnParamDecl::Field => quote! { ObjectCow<crate::classes::#inner_class> },
460+
FnParamDecl::FnPublicLifetime => quote! { #impl_as_object_arg + 'a },
461+
FnParamDecl::FnInternal => {
462+
quote! { CowArg<Option<Gd<crate::classes::#inner_class>>> }
463+
}
464+
FnParamDecl::Field => {
465+
quote! { CowArg<'a, Option<Gd<crate::classes::#inner_class>>> }
466+
}
463467
}
464468
}
465469

@@ -513,11 +517,11 @@ pub(crate) fn make_arg_expr(name: &Ident, ty: &RustTy, expr: FnArgExpr) -> Token
513517
match ty {
514518
// Objects.
515519
RustTy::EngineClass { .. } => match expr {
516-
FnArgExpr::PassToFfi => quote! { #name.as_object_arg() },
517-
FnArgExpr::PassToFfiFromEx => quote! { #name.cow_as_object_arg() },
520+
FnArgExpr::PassToFfi => quote! { #name.into_arg() },
521+
FnArgExpr::PassToFfiFromEx => quote! { #name },
518522
FnArgExpr::Forward => quote! { #name },
519-
FnArgExpr::StoreInField => quote! { #name.consume_arg() },
520-
FnArgExpr::StoreInDefaultField => quote! { #name.consume_arg() },
523+
FnArgExpr::StoreInField => quote! { #name.into_arg() },
524+
FnArgExpr::StoreInDefaultField => quote! { #name.into_arg() },
521525
},
522526

523527
// Strings.

godot-codegen/src/models/domain.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ impl FnParam {
541541
}
542542
}
543543

544-
/// `impl AsObjectArg<T>` for object parameters. Only set if requested and `T` is an engine class.
544+
/// `impl AsArg<Gd<T>>` for object parameters. Only set if requested and `T` is an engine class.
545545
pub fn new_no_defaults(method_arg: &JsonMethodArg, ctx: &mut Context) -> FnParam {
546546
FnParam {
547547
name: safe_ident(&method_arg.name),
@@ -653,10 +653,7 @@ pub enum RustTy {
653653
/// Tokens with full `Gd<T>` (e.g. used in return type position).
654654
tokens: TokenStream,
655655

656-
/// Tokens with `ObjectArg<T>` (used in `type CallSig` tuple types).
657-
object_arg: TokenStream,
658-
659-
/// Signature declaration with `impl AsObjectArg<T>`.
656+
/// Signature declaration with `impl AsArg<Gd<T>>`.
660657
impl_as_object_arg: TokenStream,
661658

662659
/// only inner `T`

godot-codegen/src/special_cases/special_cases.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,7 @@ pub fn is_class_method_const(class_name: &TyName, godot_method: &JsonClassMethod
638638
}
639639

640640
/// Currently only for virtual methods; checks if the specified parameter is required (non-null) and can be declared as `Gd<T>`
641-
/// instead of `Option<Gd<T>>`.
641+
/// instead of `Option<Gd<T>>`. By default, parameters are optional since we don't have nullability information in GDExtension.
642642
pub fn is_class_method_param_required(
643643
class_name: &TyName,
644644
godot_method_name: &str,

godot-codegen/src/util.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub fn make_imports() -> TokenStream {
2525
quote! {
2626
use godot_ffi as sys;
2727
use crate::builtin::*;
28-
use crate::meta::{AsArg, AsObjectArg, ClassName, CowArg, InParamTuple, ObjectArg, ObjectCow, OutParamTuple, ParamTuple, RefArg, Signature};
28+
use crate::meta::{AsArg, ClassName, CowArg, InParamTuple, OutParamTuple, ParamTuple, RefArg, Signature};
2929
use crate::classes::native::*;
3030
use crate::classes::Object;
3131
use crate::obj::Gd;

godot-core/src/meta/args/as_arg.rs

Lines changed: 133 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::builtin::{GString, NodePath, StringName, Variant};
1111
use crate::meta::sealed::Sealed;
1212
use crate::meta::traits::GodotFfiVariant;
1313
use crate::meta::{CowArg, GodotType, ToGodot};
14+
use crate::obj::{bounds, Bounds, DynGd, Gd, GodotClass, Inherits};
1415

1516
/// Implicit conversions for arguments passed to Godot APIs.
1617
///
@@ -22,10 +23,8 @@ use crate::meta::{CowArg, GodotType, ToGodot};
2223
/// - These all implement `ToGodot<Pass = ByValue>` and typically also `Copy`.
2324
/// - `&T` for **by-ref** built-ins: `GString`, `Array`, `Dictionary`, `PackedArray`, `Variant`...
2425
/// - These all implement `ToGodot<Pass = ByRef>`.
25-
/// - `&str`, `&String` additionally for string types `GString`, `StringName`, `NodePath`.
26-
///
27-
/// See also the [`AsObjectArg`][crate::meta::AsObjectArg] trait which is specialized for object arguments. It may be merged with `AsArg`
28-
/// in the future.
26+
/// - `&str`, `&String` additionally for string types `GString`, `StringName`, `NodePath`, see [String arguments](#string-arguments).
27+
/// - `&Gd`, `Option<&Gd>` for objects, see [Object arguments](#object-arguments).
2928
///
3029
/// # Owned values vs. references
3130
/// Implicitly converting from `T` for **by-ref** built-ins is explicitly not supported, i.e. you need to pass `&variant` instead of `variant`.
@@ -35,7 +34,14 @@ use crate::meta::{CowArg, GodotType, ToGodot};
3534
/// Sometimes, you need exactly that for generic programming though: consistently pass `T` or `&T`. For this purpose, the global functions
3635
/// [`owned_into_arg()`] and [`ref_to_arg()`] are provided.
3736
///
38-
/// # Performance for strings
37+
/// # Using the trait
38+
/// `AsArg` is meant to be used from the function call site, not the declaration site. If you declare a parameter as `impl AsArg<...>` yourself,
39+
/// you can only forward it as-is to a Godot API -- there are no stable APIs to access the inner object yet.
40+
///
41+
/// The blanket implementations of `AsArg` for `T` (in case of `Pass = ByValue`) and `&T` (`Pass = ByRef`) should readily enable most use
42+
/// cases, as long as your type already supports `ToGodot`. In the majority of cases, you'll simply use by-value passing, e.g. for enums.
43+
///
44+
/// # String arguments
3945
/// Godot has three string types: [`GString`], [`StringName`] and [`NodePath`]. Conversions between those three, as well as between `String` and
4046
/// them, is generally expensive because of allocations, re-encoding, validations, hashing, etc. While this doesn't matter for a few strings
4147
/// passed to engine APIs, it can become a problematic when passing long strings in a hot loop.
@@ -48,12 +54,29 @@ use crate::meta::{CowArg, GodotType, ToGodot};
4854
/// If you want to convert between Godot's string types for the sake of argument passing, each type provides an `arg()` method, such as
4955
/// [`GString::arg()`]. You cannot use this method in other contexts.
5056
///
51-
/// # Using the trait
52-
/// `AsArg` is meant to be used from the function call site, not the declaration site. If you declare a parameter as `impl AsArg<...>` yourself,
53-
/// you can only forward it as-is to a Godot API -- there are no stable APIs to access the inner object yet.
57+
/// # Object arguments
58+
/// This section treats `AsArg<Gd<*>>`. The trait is implemented for **shared references** in multiple ways:
59+
/// - [`&Gd<T>`][crate::obj::Gd] to pass objects. Subclasses of `T` are explicitly supported.
60+
/// - [`Option<&Gd<T>>`][Option], to pass optional objects. `None` is mapped to a null argument.
61+
/// - [`Gd::null_arg()`], to pass `null` arguments without using `Option`.
5462
///
55-
/// The blanket implementations of `AsArg` for `T` (in case of `Pass = ByValue`) and `&T` (`Pass = ByRef`) should readily enable most use
56-
/// cases, as long as your type already supports `ToGodot`. In the majority of cases, you'll simply use by-value passing, e.g. for enums.
63+
/// The following table lists the possible argument types and how you can pass them. `Gd` is short for `Gd<T>`.
64+
///
65+
/// | Type | Closest accepted type | How to transform |
66+
/// |-------------------|-----------------------|------------------|
67+
/// | `Gd` | `&Gd` | `&arg` |
68+
/// | `&Gd` | `&Gd` | `arg` |
69+
/// | `&mut Gd` | `&Gd` | `&*arg` |
70+
/// | `Option<Gd>` | `Option<&Gd>` | `arg.as_ref()` |
71+
/// | `Option<&Gd>` | `Option<&Gd>` | `arg` |
72+
/// | `Option<&mut Gd>` | `Option<&Gd>` | `arg.as_deref()` |
73+
/// | (null literal) | | `Gd::null_arg()` |
74+
///
75+
/// ## Nullability
76+
/// <div class="warning">
77+
/// The GDExtension API does not inform about nullability of its function parameters. It is up to you to verify that the arguments you pass
78+
/// are only null when this is allowed. Doing this wrong should be safe, but can lead to the function call failing.
79+
/// </div>
5780
#[diagnostic::on_unimplemented(
5881
message = "Argument of type `{Self}` cannot be passed to an `impl AsArg<{T}>` parameter",
5982
note = "if you pass by value, consider borrowing instead.",
@@ -97,6 +120,106 @@ where
97120
}
98121
}
99122

123+
// ----------------------------------------------------------------------------------------------------------------------------------------------
124+
// Object (Gd + DynGd) impls
125+
126+
// TODO(v0.4): all objects + optional objects should be pass-by-ref.
127+
128+
impl<T, Base> AsArg<Gd<Base>> for &Gd<T>
129+
where
130+
T: Inherits<Base>,
131+
Base: GodotClass,
132+
{
133+
fn into_arg<'r>(self) -> CowArg<'r, Gd<Base>>
134+
where
135+
Self: 'r,
136+
{
137+
CowArg::Owned(self.clone().upcast::<Base>())
138+
}
139+
}
140+
141+
impl<T, U, D> AsArg<DynGd<T, D>> for &DynGd<U, D>
142+
where
143+
T: GodotClass,
144+
U: Inherits<T>,
145+
D: ?Sized,
146+
{
147+
fn into_arg<'r>(self) -> CowArg<'r, DynGd<T, D>>
148+
where
149+
Self: 'r,
150+
{
151+
CowArg::Owned(self.clone().upcast::<T>())
152+
}
153+
}
154+
155+
// Convert DynGd -> Gd (with upcast).
156+
impl<'r, T, U, D> AsArg<Gd<T>> for &'r DynGd<U, D>
157+
where
158+
T: GodotClass,
159+
U: Inherits<T>,
160+
D: ?Sized,
161+
{
162+
fn into_arg<'cow>(self) -> CowArg<'cow, Gd<T>>
163+
where
164+
'r: 'cow,
165+
{
166+
CowArg::Owned(self.clone().upcast::<T>().into_gd())
167+
}
168+
}
169+
170+
// ----------------------------------------------------------------------------------------------------------------------------------------------
171+
// Optional object (Gd + DynGd) impls
172+
173+
impl<T, U> AsArg<Option<Gd<T>>> for &Option<Gd<U>>
174+
where
175+
T: GodotClass + Bounds<Declarer = bounds::DeclEngine>,
176+
U: Inherits<T>,
177+
{
178+
fn into_arg<'r>(self) -> CowArg<'r, Option<Gd<T>>> {
179+
match self {
180+
Some(gd) => CowArg::Owned(Some(gd.clone().upcast::<T>())),
181+
None => CowArg::Owned(None),
182+
}
183+
}
184+
}
185+
186+
impl<T, U> AsArg<Option<Gd<T>>> for Option<&Gd<U>>
187+
where
188+
T: GodotClass + Bounds<Declarer = bounds::DeclEngine>,
189+
U: Inherits<T>,
190+
{
191+
fn into_arg<'cow>(self) -> CowArg<'cow, Option<Gd<T>>> {
192+
// This needs to construct a new Option<Gd<T>>, so cloning is unavoidable
193+
// since we go from Option<&Gd<U>> to Option<Gd<T>>
194+
match self {
195+
Some(gd) => CowArg::Owned(Some(gd.clone().upcast::<T>())),
196+
None => CowArg::Owned(None),
197+
}
198+
}
199+
}
200+
201+
impl<T, U> AsArg<Option<Gd<T>>> for &Gd<U>
202+
where
203+
T: GodotClass + Bounds<Declarer = bounds::DeclEngine>,
204+
U: Inherits<T>,
205+
{
206+
fn into_arg<'cow>(self) -> CowArg<'cow, Option<Gd<T>>> {
207+
CowArg::Owned(Some(self.clone().upcast::<T>()))
208+
}
209+
}
210+
211+
impl<T, U, D> AsArg<Option<Gd<T>>> for &DynGd<U, D>
212+
where
213+
T: GodotClass + Bounds<Declarer = bounds::DeclEngine>,
214+
U: Inherits<T>,
215+
D: ?Sized,
216+
{
217+
fn into_arg<'cow>(self) -> CowArg<'cow, Option<Gd<T>>> {
218+
let gd: &Gd<U> = self; // Deref
219+
CowArg::Owned(Some(gd.clone().upcast::<T>()))
220+
}
221+
}
222+
100223
// ----------------------------------------------------------------------------------------------------------------------------------------------
101224
// Public helper functions (T|&T -> AsArg)
102225

godot-core/src/meta/args/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@ pub use as_arg::{owned_into_arg, ref_to_arg, ArgPassing, AsArg, ByRef, ByValue,
2323
pub use cow_arg::CowArg;
2424
#[cfg(not(feature = "trace"))]
2525
pub(crate) use cow_arg::CowArg;
26-
pub use object_arg::AsObjectArg;
27-
#[allow(unused_imports)] // ObjectCow is used in generated code.
28-
pub(crate) use object_arg::{ObjectArg, ObjectCow, ObjectNullArg};
26+
#[allow(unused)] // TODO(v0.4): replace contents with newer changes
27+
pub use object_arg::ObjectArg;
2928
pub use ref_arg::RefArg;
3029

3130
// #[doc(hidden)]

0 commit comments

Comments
 (0)