Skip to content
Merged
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
35 changes: 1 addition & 34 deletions godot-codegen/src/generator/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,34 +232,6 @@ fn make_special_builtin_methods(class_name: &TyName, _ctx: &Context) -> TokenStr
}
}

/// Get the safety docs of an unsafe method, or `None` if it is safe.
fn method_safety_doc(class_name: &TyName, method: &BuiltinMethod) -> Option<TokenStream> {
if class_name.godot_ty == "Array" {
if method.is_generic() {
return Some(quote! {
/// # Safety
/// You must ensure that the returned array fulfils the safety invariants of [`Array`](crate::builtin::Array), this being:
/// - Any values written to the array must match the runtime type of the array.
/// - Any values read from the array must be convertible to the type `T`.
///
/// If the safety invariant of `Array` is intact, which it must be for any publicly accessible arrays, then `T` must match
/// the runtime type of the array. This then implies that both of the conditions above hold. This means that you only need
/// to keep the above conditions in mind if you are intentionally violating the safety invariant of `Array`.
///
/// In the current implementation, both cases will produce a panic rather than undefined behavior, but this should not be relied upon.
});
} else if &method.return_value().type_tokens().to_string() == "VarArray" {
return Some(quote! {
/// # Safety
///
/// You must ensure that the returned array fulfils the safety invariants of [`Array`](crate::builtin::Array).
});
}
}

None
}

fn make_builtin_method_definition(
builtin_class: &BuiltinClass,
variant_shout_name: &Ident,
Expand Down Expand Up @@ -302,13 +274,11 @@ fn make_builtin_method_definition(
let receiver = functions_common::make_receiver(method.qualifier(), ffi_arg_in);
let object_ptr = &receiver.ffi_arg;

let maybe_generic_params = method.return_value().generic_params();

let ptrcall_invocation = quote! {
let method_bind = sys::builtin_method_table().#fptr_access;


Signature::<CallParams, CallRet #maybe_generic_params>::out_builtin_ptrcall(
Signature::<CallParams, CallRet>::out_builtin_ptrcall(
method_bind,
#builtin_name_str,
#method_name_str,
Expand All @@ -330,8 +300,6 @@ fn make_builtin_method_definition(
)
};

let safety_doc = method_safety_doc(builtin_class.name(), method);

functions_common::make_function_definition(
method,
&FnCode {
Expand All @@ -341,7 +309,6 @@ fn make_builtin_method_definition(
is_virtual_required: false,
is_varcall_fallible: false,
},
safety_doc,
&TokenStream::new(),
)
}
1 change: 0 additions & 1 deletion godot-codegen/src/generator/classes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,6 @@ fn make_class_method_definition(
is_virtual_required: false,
is_varcall_fallible: true,
},
None,
cfg_attributes,
)
}
33 changes: 13 additions & 20 deletions godot-codegen/src/generator/functions_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ pub struct FnParamTokens {
pub fn make_function_definition(
sig: &dyn Function,
code: &FnCode,
safety_doc: Option<TokenStream>,
cfg_attributes: &TokenStream,
) -> FnDefinition {
let has_default_params = default_parameters::function_uses_default_params(sig);
Expand All @@ -120,20 +119,18 @@ pub fn make_function_definition(
// to only use `unsafe` for pointers in parameters (for outbound calls), and in return values (for virtual calls). Or technically more
// correct, make the entire trait unsafe as soon as one function can return pointers, but that's very unergonomic and non-local.
// Thus, let's keep things simple and more conservative.
let (maybe_unsafe, maybe_safety_doc) = if let Some(safety_doc) = safety_doc {
(quote! { unsafe }, safety_doc)
} else if sig.common().is_unsafe {
(
quote! { unsafe },
quote! {
/// # Safety
///
/// This method has automatically been marked `unsafe` because it accepts raw pointers as parameters.
/// If Godot does not document any safety requirements, make sure you understand the underlying semantics.
},
)
let (maybe_unsafe, maybe_safety_doc);
if sig.common().is_unsafe {
maybe_unsafe = quote! { unsafe };
maybe_safety_doc = quote! {
/// # Safety
///
/// This method has automatically been marked `unsafe` because it accepts raw pointers as parameters.
/// If Godot does not document any safety requirements, make sure you understand the underlying semantics.
};
} else {
(TokenStream::new(), TokenStream::new())
maybe_unsafe = TokenStream::new();
maybe_safety_doc = TokenStream::new();
};

let FnParamTokens {
Expand Down Expand Up @@ -173,15 +170,13 @@ pub fn make_function_definition(
default_structs_code = TokenStream::new();
};

let maybe_func_generic_params = sig.return_value().generic_params();
let maybe_func_generic_bounds = sig.return_value().where_clause();
let (maybe_deprecated, _maybe_expect_deprecated) = make_deprecation_attribute(sig);

let call_sig_decl = {
let return_ty = &sig.return_value().type_tokens();

quote! {
type CallRet #maybe_func_generic_params = #return_ty;
type CallRet = #return_ty;
type CallParams #callsig_lifetime_args = (#(#param_types,)*);
}
};
Expand Down Expand Up @@ -286,11 +281,10 @@ pub fn make_function_definition(
quote! {
#maybe_deprecated
#maybe_safety_doc
#vis #maybe_unsafe fn #primary_fn_name #maybe_func_generic_params (
#vis #maybe_unsafe fn #primary_fn_name (
#receiver_param
#( #params, )*
) #return_decl
#maybe_func_generic_bounds
{
#call_sig_decl

Expand Down Expand Up @@ -510,7 +504,6 @@ pub(crate) fn make_param_or_field_type(
..
}
| RustTy::BuiltinArray { .. }
| RustTy::GenericArray
| RustTy::EngineArray { .. } => {
let lft = lifetimes.next();
special_ty = Some(quote! { RefArg<#lft, #ty> });
Expand Down
1 change: 0 additions & 1 deletion godot-codegen/src/generator/utility_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ pub(crate) fn make_utility_function_definition(function: &UtilityFunction) -> To
is_virtual_required: false,
is_varcall_fallible: false,
},
None,
&TokenStream::new(),
);

Expand Down
1 change: 0 additions & 1 deletion godot-codegen/src/generator/virtual_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,6 @@ fn make_virtual_method(
is_virtual_required,
is_varcall_fallible: true,
},
None,
&TokenStream::new(),
);

Expand Down
49 changes: 1 addition & 48 deletions godot-codegen/src/models/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,10 +332,6 @@ pub trait Function: fmt::Display {
matches!(self.direction(), FnDirection::Virtual { .. })
}

fn is_generic(&self) -> bool {
matches!(self.return_value().type_, Some(RustTy::GenericArray))
}

fn direction(&self) -> FnDirection {
self.common().direction
}
Expand Down Expand Up @@ -681,13 +677,6 @@ impl FnReturn {
Self::with_enum_replacements(return_value, &[], flow, ctx)
}

pub fn with_generic_builtin(generic_type: RustTy) -> Self {
Self {
decl: generic_type.return_decl(),
type_: Some(generic_type),
}
}

pub fn with_enum_replacements(
return_value: &Option<JsonMethodReturn>,
replacements: EnumReplacements,
Expand Down Expand Up @@ -730,14 +719,6 @@ impl FnReturn {
}
}

pub fn generic_params(&self) -> Option<TokenStream> {
self.type_.as_ref()?.generic_params()
}

pub fn where_clause(&self) -> Option<TokenStream> {
self.type_.as_ref()?.where_clause()
}

pub fn call_result_decl(&self) -> TokenStream {
let ret = self.type_tokens();
quote! { -> Result<#ret, crate::meta::error::CallError> }
Expand Down Expand Up @@ -781,11 +762,6 @@ pub enum RustTy {
/// Untyped arrays are either `BuiltinIdent("AnyArray")` for outbound methods, or `BuiltinIdent("Array")` for virtual methods.
BuiltinArray { elem_type: TokenStream },

/// Will be included as `Array<T>` in the generated source.
///
/// Set by [`builtin_method_generic_ret`](crate::special_cases::builtin_method_generic_ret)
GenericArray,

/// C-style raw pointer to a `RustTy`.
RawPointer { inner: Box<RustTy>, is_const: bool },

Expand Down Expand Up @@ -840,10 +816,7 @@ impl RustTy {
}

pub fn return_decl(&self) -> TokenStream {
match self {
Self::GenericArray => quote! { -> Array<Ret> },
_ => quote! { -> #self },
}
quote! { -> #self }
}

/// Returns tokens without `Option<T>` wrapper, even for nullable engine classes.
Expand All @@ -857,25 +830,6 @@ impl RustTy {
}
}

pub fn generic_params(&self) -> Option<TokenStream> {
if matches!(self, Self::GenericArray) {
Some(quote! { < Ret > })
} else {
None
}
}

pub fn where_clause(&self) -> Option<TokenStream> {
if matches!(self, Self::GenericArray) {
Some(quote! {
where
Ret: crate::meta::ArrayElement,
})
} else {
None
}
}

pub fn is_integer(&self) -> bool {
let RustTy::BuiltinIdent { ty, .. } = self else {
return false;
Expand Down Expand Up @@ -924,7 +878,6 @@ impl ToTokens for RustTy {
}
}
RustTy::ExtenderReceiver { tokens: path } => path.to_tokens(tokens),
RustTy::GenericArray => quote! { Array<Ret> }.to_tokens(tokens),
RustTy::SysPointerType { tokens: path } => path.to_tokens(tokens),
}
}
Expand Down
36 changes: 22 additions & 14 deletions godot-codegen/src/models/domain_mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,27 +367,35 @@ impl BuiltinMethod {
return None;
}

let return_value = match special_cases::builtin_method_generic_ret(builtin_name, method) {
Some(generic) => generic,
_ => {
let return_value = &method
.return_type
.as_deref()
.map(JsonMethodReturn::from_type_no_meta);

// Builtin methods are always outbound (not virtual), thus flow for return type is Godot -> Rust.
FnReturn::new(return_value, FlowDirection::GodotToRust, ctx)
}
let is_exposed_in_outer =
special_cases::is_builtin_method_exposed(builtin_name, &method.name);

let return_value = {
let return_value = &method
.return_type
.as_deref()
.map(JsonMethodReturn::from_type_no_meta);

// Builtin methods are always outbound (not virtual), thus flow for return type is Godot -> Rust.
// Exception: Inner{Array,Dictionary} methods return Any{Array,Dictionary} instead of Var{Array,Dictionary}. Reason is that
// arrays/dicts can be generic and store type info, thus typing returned collections differently. Thus use RustToGodot flow.
let flow = if !is_exposed_in_outer
&& matches!(builtin_name.godot_ty.as_str(), "Array" | "Dictionary")
&& matches!(method.return_type.as_deref(), Some("Array" | "Dictionary"))
{
FlowDirection::RustToGodot // AnyArray + AnyDictionary.
} else {
FlowDirection::GodotToRust
};

FnReturn::new(return_value, flow, ctx)
};

// For parameters in builtin methods, flow is always Rust -> Godot.
// Enable default parameters for builtin classes, generating _ex builders.
let parameters =
FnParam::builder().build_many(&method.arguments, FlowDirection::RustToGodot, ctx);

let is_exposed_in_outer =
special_cases::is_builtin_method_exposed(builtin_name, &method.name);

// Construct surrounding_class with correct type names:
// * godot_ty: Always the real Godot type (e.g. "String").
// * rust_ty: Rust struct where the method is declared ("GString" for exposed, "InnerString" for private one).
Expand Down
1 change: 0 additions & 1 deletion godot-codegen/src/special_cases/codegen_special_cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ fn is_type_excluded(ty: &str, ctx: &mut Context) -> bool {
match ty {
RustTy::BuiltinIdent { .. } => false,
RustTy::BuiltinArray { .. } => false,
RustTy::GenericArray => false,
RustTy::RawPointer { inner, .. } => is_rust_type_excluded(inner),
RustTy::SysPointerType { .. } => true,
RustTy::EngineArray { elem_class, .. } => is_class_excluded(elem_class.as_str()),
Expand Down
23 changes: 1 addition & 22 deletions godot-codegen/src/special_cases/special_cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use proc_macro2::Ident;
use crate::Context;
use crate::conv::to_enum_type_uncached;
use crate::models::domain::{
ClassCodegenLevel, Enum, EnumReplacements, FnReturn, RustTy, TyName, VirtualMethodPresence,
ClassCodegenLevel, Enum, EnumReplacements, RustTy, TyName, VirtualMethodPresence,
};
use crate::models::json::{JsonBuiltinMethod, JsonClassMethod, JsonSignal, JsonUtilityFunction};
use crate::special_cases::codegen_special_cases;
Expand Down Expand Up @@ -823,27 +823,6 @@ pub fn is_builtin_method_deleted(_class_name: &TyName, method: &JsonBuiltinMetho
codegen_special_cases::is_builtin_method_excluded(method)
}

/// Returns some generic type – such as `GenericArray` representing `Array<T>` – if method is marked as generic, `None` otherwise.
///
/// Usually required to initialize the return value and cache its type (see also https://github.com/godot-rust/gdext/pull/1357).
#[rustfmt::skip]
pub fn builtin_method_generic_ret(
class_name: &TyName,
method: &JsonBuiltinMethod,
) -> Option<FnReturn> {
match (
class_name.rust_ty.to_string().as_str(),
method.name.as_str(),
) {
| ("Array", "duplicate")
| ("Array", "slice")
| ("Array", "filter")

=> Some(FnReturn::with_generic_builtin(RustTy::GenericArray)),
_ => None,
}
}

/// True if signal is absent from codegen (only when surrounding class is excluded).
pub fn is_signal_deleted(_class_name: &TyName, signal: &JsonSignal) -> bool {
// If any argument type (a class) is excluded.
Expand Down
Loading
Loading