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

Allow virtual methods to be GdSelf in special cases #1048

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions godot-codegen/src/generator/default_parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,8 @@ fn make_extender_receiver(sig: &dyn Function) -> ExtenderReceiver {
let builder_mut = match sig.qualifier() {
FnQualifier::Const | FnQualifier::Static => quote! {},
FnQualifier::Mut => quote! { mut },
FnQualifier::Global => {
unreachable!("default parameters not supported for global methods; {sig}")
FnQualifier::Global | FnQualifier::GdSelf => {
unreachable!("default parameters not supported for global or gdself methods; {sig}")
}
};

Expand Down
4 changes: 4 additions & 0 deletions godot-codegen/src/generator/functions_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ pub fn make_receiver(qualifier: FnQualifier, ffi_arg_in: TokenStream) -> FnRecei
let (param, param_lifetime_a) = match qualifier {
FnQualifier::Const => (quote! { &self, }, quote! { &'a self, }),
FnQualifier::Mut => (quote! { &mut self, }, quote! { &'a mut self, }),
FnQualifier::GdSelf => (quote! { this: Gd<Self>, }, quote! { this: Gd<Self>, }),
FnQualifier::Static => (quote! {}, quote! {}),
FnQualifier::Global => (quote! {}, quote! {}),
};
Expand All @@ -316,6 +317,9 @@ pub fn make_receiver(qualifier: FnQualifier, ffi_arg_in: TokenStream) -> FnRecei
if matches!(qualifier, FnQualifier::Static) {
ffi_arg = quote! { std::ptr::null_mut() };
self_prefix = quote! { Self:: };
} else if matches!(qualifier, FnQualifier::Static) {
ffi_arg = ffi_arg_in;
self_prefix = quote! { this. };
} else {
ffi_arg = ffi_arg_in;
self_prefix = quote! { self. };
Expand Down
5 changes: 4 additions & 1 deletion godot-codegen/src/generator/virtual_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,10 @@ fn make_virtual_method(method: &ClassMethod) -> Option<TokenStream> {

// Virtual methods are never static.
let qualifier = method.qualifier();
assert!(matches!(qualifier, FnQualifier::Mut | FnQualifier::Const));
assert!(matches!(
qualifier,
FnQualifier::Mut | FnQualifier::Const | FnQualifier::GdSelf
));

let definition = functions_common::make_function_definition(
method,
Expand Down
1 change: 1 addition & 0 deletions godot-codegen/src/models/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ pub enum FnDirection {
pub enum FnQualifier {
Mut, // &mut self
Const, // &self
GdSelf, // Gd<Self>
Static, // Self
Global, // (nothing) // TODO remove?
}
Expand Down
7 changes: 6 additions & 1 deletion godot-codegen/src/models/domain_mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ impl ClassMethod {
}

let is_private = special_cases::is_method_private(class_name, &method.name);
let is_gdself = special_cases::is_virtual_method_gdself(class_name, rust_method_name);

let godot_method_name = method.name.clone();

Expand All @@ -468,7 +469,11 @@ impl ClassMethod {
is_actually_const = override_const;
}

FnQualifier::from_const_static(is_actually_const, method.is_static)
if is_gdself {
FnQualifier::GdSelf
} else {
FnQualifier::from_const_static(is_actually_const, method.is_static)
}
};

// Since Godot 4.4, GDExtension advertises whether virtual methods have a default implementation or are required to be overridden.
Expand Down
13 changes: 13 additions & 0 deletions godot-codegen/src/special_cases/special_cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,19 @@ pub fn is_enum_exhaustive(class_name: Option<&TyName>, enum_name: &str) -> bool
}
}

/// Certain virtual methods can make it very difficult to avoid double bind issues
/// if they take &self or &mut self.
///
/// These methods take Gd<Self> as a receiver so the implementer can decide when to bind
/// and unbind them.
pub fn is_virtual_method_gdself(class_name: &TyName, method: &str) -> bool {
match (class_name.godot_ty.as_str(), method) {
("MultiplayerAPIExtension", "poll") => true,

_ => false,
}
}

/// Whether an enum can be combined with another enum (return value) for bitmasking purposes.
///
/// If multiple masks are ever necessary, this can be extended to return a slice instead of Option.
Expand Down
12 changes: 10 additions & 2 deletions godot-macros/src/class/data_models/interface_trait_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::{util, ParseResult};

use proc_macro2::{Ident, TokenStream};
use quote::quote;
use venial::FnParam;

/// Codegen for `#[godot_api] impl ISomething for MyType`
pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStream> {
Expand Down Expand Up @@ -211,7 +212,7 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStr
method_name_str => {
#[cfg(since_api = "4.4")]
let method_name_ident = method.name.clone();
let method = util::reduce_to_signature(method);
let mut method = util::reduce_to_signature(method);

// Godot-facing name begins with underscore.
//
Expand All @@ -223,7 +224,14 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStr
format!("_{method_name_str}")
};

let signature_info = into_signature_info(method, &class_name, false);
// Some virtual methods are GdSelf, but none are static, so if the first param isn't a receiver (&self, etc) it's GdSelf
let is_gd_self = matches!(method.params.first(), Some((FnParam::Typed(_), _)));
if is_gd_self {
// The GdSelf receiver is handled by `into_signature_info`
method.params.inner.remove(0);
}

let signature_info = into_signature_info(method, &class_name, is_gd_self);

// Overridden ready() methods additionally have an additional `__before_ready()` call (for OnReady inits).
let before_kind = if method_name_str == "ready" {
Expand Down
Loading