diff --git a/check.sh b/check.sh index 224a3b32e..9c96f8f92 100755 --- a/check.sh +++ b/check.sh @@ -26,9 +26,10 @@ If no commands are specified, the following commands will be run: Commands: fmt format code, fail if bad - clippy validate clippy lints test run unit tests (no Godot needed) itest run integration tests (from within Godot) + clippy validate clippy lints + klippy validate + fix clippy doc generate docs for 'godot' crate dok generate docs and open in browser @@ -137,6 +138,18 @@ function cmd_clippy() { -D warnings } +function cmd_klippy() { + run cargo clippy --fix --all-targets "${extraCargoArgs[@]}" -- \ + -D clippy::suspicious \ + -D clippy::style \ + -D clippy::complexity \ + -D clippy::perf \ + -D clippy::dbg_macro \ + -D clippy::todo \ + -D clippy::unimplemented \ + -D warnings +} + function cmd_test() { run cargo test "${extraCargoArgs[@]}" } @@ -178,7 +191,7 @@ for arg in "$@"; do --double) extraCargoArgs+=("--features" "godot/double-precision") ;; - fmt | clippy | test | itest | doc | dok) + fmt | test | itest | clippy | klippy | doc | dok) cmds+=("$arg") ;; -f | --filter) diff --git a/godot-codegen/src/central_generator.rs b/godot-codegen/src/central_generator.rs index 9e07cae6a..c1b8835c1 100644 --- a/godot-codegen/src/central_generator.rs +++ b/godot-codegen/src/central_generator.rs @@ -7,16 +7,14 @@ use proc_macro2::{Ident, Literal, TokenStream}; use quote::{format_ident, quote, ToTokens, TokenStreamExt}; -use std::collections::HashMap; -use std::hash::Hasher; use std::path::Path; -use crate::api_parser::*; -use crate::util::{ - make_builtin_method_ptr_name, make_class_method_ptr_name, option_as_slice, ClassCodegenLevel, - MethodTableKey, +use crate::models::domain::{ + BuiltinMethod, BuiltinVariant, Class, ClassLike, ClassMethod, Constructor, Enumerator, + ExtensionApi, FnDirection, Function, GodotApiVersion, Operator, TyName, }; -use crate::{codegen_special_cases, conv, ident, special_cases, util, Context, SubmitFn, TyName}; +use crate::util::{make_table_accessor_name, ClassCodegenLevel, MethodTableKey}; +use crate::{conv, ident, special_cases, util, Context, SubmitFn}; struct CentralItems { opaque_types: [Vec; 2], @@ -26,7 +24,7 @@ struct CentralItems { variant_op_enumerators_pascal: Vec, variant_op_enumerators_ord: Vec, global_enum_defs: Vec, - godot_version: Header, + godot_version: GodotApiVersion, } struct NamedMethodTable { @@ -116,85 +114,14 @@ struct AccessorMethod { // ---------------------------------------------------------------------------------------------------------------------------------------------- -pub struct BuiltinName { - /// Name in JSON: "int" or "PackedVector2Array" - pub json_builtin_name: String, - - /// "packed_vector2_array" - pub snake_case: String, - - /// "PACKED_VECTOR2_ARRAY" - //pub shout_case: String, - - /// GDEXTENSION_VARIANT_TYPE_PACKED_VECTOR2_ARRAY - pub sys_variant_type: Ident, -} - -impl Eq for BuiltinName {} - -impl PartialEq for BuiltinName { - fn eq(&self, other: &Self) -> bool { - self.json_builtin_name == other.json_builtin_name - } -} - -impl std::hash::Hash for BuiltinName { - fn hash(&self, state: &mut H) { - self.json_builtin_name.hash(state); - } -} - -// ---------------------------------------------------------------------------------------------------------------------------------------------- - -/// Allows collecting all builtin TypeNames before generating methods -pub(crate) struct BuiltinTypeInfo<'a> { - pub variant_type_ord: i32, - pub type_names: BuiltinName, - - /// If `variant_get_ptr_destructor` returns a non-null function pointer for this type. - /// List is directly sourced from extension_api.json (information would also be in variant_destruct.cpp). - pub has_destructor: bool, - pub constructors: Option<&'a Vec>, - pub operators: Option<&'a Vec>, -} - -// ---------------------------------------------------------------------------------------------------------------------------------------------- - -pub(crate) struct BuiltinTypeMap<'a> { - map: HashMap>, -} - -impl<'a> BuiltinTypeMap<'a> { - pub fn load(api: &'a ExtensionApi) -> Self { - Self { - map: collect_builtin_types(api), - } - } - - /// Returns an iterator over the builtin types, ordered by `VariantType` value. - fn ordered(&self) -> impl Iterator> { - let mut ordered: Vec<_> = self.map.values().collect(); - ordered.sort_by_key(|info| info.variant_type_ord); - ordered.into_iter() - } - - fn count(&self) -> usize { - self.map.len() - } -} - -// ---------------------------------------------------------------------------------------------------------------------------------------------- - pub(crate) fn generate_sys_central_file( api: &ExtensionApi, ctx: &mut Context, - build_config: [&str; 2], sys_gen_path: &Path, submit_fn: &mut SubmitFn, ) { - let builtin_types = BuiltinTypeMap::load(api); - let central_items = make_central_items(api, build_config, builtin_types, ctx); - let sys_code = make_sys_code(¢ral_items); + let central_items = make_central_items(api, ctx); + let sys_code = make_sys_code(central_items); submit_fn(sys_gen_path.join("central.rs"), sys_code); } @@ -218,7 +145,6 @@ pub(crate) fn generate_sys_classes_file( pub(crate) fn generate_sys_utilities_file( api: &ExtensionApi, sys_gen_path: &Path, - ctx: &mut Context, submit_fn: &mut SubmitFn, ) { let mut table = NamedMethodTable { @@ -239,13 +165,9 @@ pub(crate) fn generate_sys_utilities_file( }; for function in api.utility_functions.iter() { - if codegen_special_cases::is_function_excluded(function, ctx) { - continue; - } - - let fn_name_str = &function.name; - let field = util::make_utility_function_ptr_name(function); - let hash = function.hash; + let fn_name_str = function.name(); + let field = util::make_utility_function_ptr_name(fn_name_str); + let hash = function.hash(); table.method_decls.push(quote! { pub #field: crate::UtilityFunctionBind, @@ -333,13 +255,13 @@ fn make_method_table(info: IndexedMethodTable) -> TokenStream { method_count, "number of methods does not match count" ); - // method_inits.sort_by_key(|init| init.index); if let Some(last) = method_init_groups.last() { assert_eq!( last.method_inits.last().unwrap().index, method_count - 1, - "last method should have highest index" + "last method should have highest index (table {})", + table_name ); } else { assert_eq!(method_count, 0, "empty method table should have count 0"); @@ -490,27 +412,25 @@ fn make_method_table(info: IndexedMethodTable) -> TokenStream { pub(crate) fn generate_sys_builtin_methods_file( api: &ExtensionApi, - builtin_types: &BuiltinTypeMap, sys_gen_path: &Path, ctx: &mut Context, submit_fn: &mut SubmitFn, ) { - let code = make_builtin_method_table(api, builtin_types, ctx); + let code = make_builtin_method_table(api, ctx); submit_fn(sys_gen_path.join("table_builtins.rs"), code); } pub(crate) fn generate_sys_builtin_lifecycle_file( - builtin_types: &BuiltinTypeMap, + api: &ExtensionApi, sys_gen_path: &Path, submit_fn: &mut SubmitFn, ) { - // TODO merge this and the one in central.rs, to only collect once - let code = make_builtin_lifecycle_table(builtin_types); + let code = make_builtin_lifecycle_table(api); submit_fn(sys_gen_path.join("table_builtins_lifecycle.rs"), code); } pub(crate) fn generate_core_mod_file(gen_path: &Path, submit_fn: &mut SubmitFn) { - // When invoked by another crate during unit-test (not integration test), don't run generator + // When invoked by another crate during unit-test (not integration test), don't run generator. let code = quote! { pub mod central; pub mod classes; @@ -525,18 +445,16 @@ pub(crate) fn generate_core_mod_file(gen_path: &Path, submit_fn: &mut SubmitFn) pub(crate) fn generate_core_central_file( api: &ExtensionApi, ctx: &mut Context, - build_config: [&str; 2], gen_path: &Path, submit_fn: &mut SubmitFn, ) { - let builtin_types = BuiltinTypeMap::load(api); - let central_items = make_central_items(api, build_config, builtin_types, ctx); + let central_items = make_central_items(api, ctx); let core_code = make_core_code(¢ral_items); submit_fn(gen_path.join("central.rs"), core_code); } -fn make_sys_code(central_items: &CentralItems) -> TokenStream { +fn make_sys_code(central_items: CentralItems) -> TokenStream { let CentralItems { opaque_types, variant_ty_enumerators_pascal, @@ -547,7 +465,7 @@ fn make_sys_code(central_items: &CentralItems) -> TokenStream { .. } = central_items; - let build_config_struct = make_build_config(godot_version); + let build_config_struct = make_build_config(&godot_version); let [opaque_32bit, opaque_64bit] = opaque_types; quote! { @@ -581,7 +499,7 @@ fn make_sys_code(central_items: &CentralItems) -> TokenStream { impl VariantType { #[doc(hidden)] pub fn from_sys(enumerator: GDExtensionVariantType) -> Self { - // Annoying, but only stable alternative is transmute(), which dictates enum size + // Annoying, but only stable alternative is transmute(), which dictates enum size. match enumerator { 0 => Self::Nil, #( @@ -626,14 +544,13 @@ fn make_sys_code(central_items: &CentralItems) -> TokenStream { } } -fn make_build_config(header: &Header) -> TokenStream { - let version_string = header - .version_full_name - .strip_prefix("Godot Engine ") - .unwrap_or(&header.version_full_name); - let major = header.version_major; - let minor = header.version_minor; - let patch = header.version_patch; +fn make_build_config(header: &GodotApiVersion) -> TokenStream { + let GodotApiVersion { + major, + minor, + patch, + version_string, + } = header; // Should this be mod? quote! { @@ -710,7 +627,7 @@ fn make_core_code(central_items: &CentralItems) -> TokenStream { // TODO impl Clone, Debug, PartialEq, PartialOrd, Hash for VariantDispatch // TODO could use try_to().unwrap_unchecked(), since type is already verified. Also directly overload from_variant(). - // But this requires that all the variant types support this + // But this requires that all the variant types support this. quote! { use crate::builtin::*; use crate::engine::Object; @@ -752,29 +669,20 @@ fn make_core_code(central_items: &CentralItems) -> TokenStream { } } -fn make_central_items( - api: &ExtensionApi, - build_config: [&str; 2], - builtin_types: BuiltinTypeMap, - ctx: &mut Context, -) -> CentralItems { +fn make_central_items(api: &ExtensionApi, ctx: &mut Context) -> CentralItems { let mut opaque_types = [Vec::new(), Vec::new()]; - for class in &api.builtin_class_sizes { - for i in 0..2 { - if class.build_configuration == build_config[i] { - for ClassSize { name, size } in &class.sizes { - opaque_types[i].push(make_opaque_type(name, *size)); - } - break; - } - } + + for b in api.builtin_sizes.iter() { + let index = b.config.is_64bit() as usize; + + opaque_types[index].push(make_opaque_type(&b.builtin_original_name, b.size)); } let variant_operators = collect_variant_operators(api); // Generate builtin methods, now with info for all types available. // Separate vectors because that makes usage in quote! easier. - let len = builtin_types.count(); + let len = api.builtins.len(); let mut result = CentralItems { opaque_types, @@ -784,58 +692,57 @@ fn make_central_items( variant_op_enumerators_pascal: Vec::new(), variant_op_enumerators_ord: Vec::new(), global_enum_defs: Vec::new(), - godot_version: api.header.clone(), + godot_version: api.godot_version.clone(), }; - // Note: NIL is not part of this iteration, it will be added manually - for ty in builtin_types.ordered() { - let (pascal_name, rust_ty, ord) = make_enumerator(&ty.type_names, ty.variant_type_ord, ctx); + // Note: NIL is not part of this iteration, it will be added manually. + for builtin in api.builtins.iter() { + let original_name = builtin.godot_original_name(); + let rust_ty = conv::to_rust_type(original_name, None, ctx); + let pascal_case = conv::to_pascal_case(original_name); + let ord = builtin.unsuffixed_ord_lit(); - result.variant_ty_enumerators_pascal.push(pascal_name); - result.variant_ty_enumerators_rust.push(rust_ty); + result + .variant_ty_enumerators_pascal + .push(ident(&pascal_case)); + result + .variant_ty_enumerators_rust + .push(rust_ty.to_token_stream()); result.variant_ty_enumerators_ord.push(ord); } for op in variant_operators { - let name = op - .name - .strip_prefix("OP_") - .expect("expected `OP_` prefix for variant operators"); + let pascal_name = conv::to_pascal_case(&op.name.to_string()); - if name == "MAX" { - continue; - } - - let op_enumerator_pascal = conv::shout_to_pascal(name); - let op_enumerator_pascal = if op_enumerator_pascal == "Module" { - "Modulo" + let enumerator_name = if pascal_name == "Module" { + ident("Modulo") } else { - &op_enumerator_pascal + ident(&pascal_name) }; - result - .variant_op_enumerators_pascal - .push(ident(op_enumerator_pascal)); + result.variant_op_enumerators_pascal.push(enumerator_name); result .variant_op_enumerators_ord - .push(util::make_enumerator_ord_unsuffixed(op.to_enum_ord())); + .push(op.value.unsuffixed_lit()); } for enum_ in api.global_enums.iter() { - // Skip those enums which are already explicitly handled - if matches!(enum_.name.as_str(), "Variant.Type" | "Variant.Operator") { + // Skip those enums which are already manually handled. + if enum_.name == "VariantType" || enum_.name == "VariantOperator" { continue; } - let def = util::make_enum_definition(enum_, None); + let def = util::make_enum_definition(enum_); result.global_enum_defs.push(def); } result } -fn make_builtin_lifecycle_table(builtin_types: &BuiltinTypeMap) -> TokenStream { - let len = builtin_types.count(); +fn make_builtin_lifecycle_table(api: &ExtensionApi) -> TokenStream { + let builtins = &api.builtins; + let len = builtins.len(); + let mut table = NamedMethodTable { table_name: ident("BuiltinLifecycleTable"), imports: quote! { @@ -861,15 +768,9 @@ fn make_builtin_lifecycle_table(builtin_types: &BuiltinTypeMap) -> TokenStream { method_count: 0, }; - // Note: NIL is not part of this iteration, it will be added manually - for ty in builtin_types.ordered() { - let (decls, inits) = make_variant_fns( - &ty.type_names, - ty.has_destructor, - ty.constructors, - ty.operators, - &builtin_types.map, - ); + // Note: NIL is not part of this iteration, it will be added manually. + for variant in builtins.iter() { + let (decls, inits) = make_variant_fns(api, variant); table.method_decls.push(decls); table.method_inits.push(inits); @@ -911,17 +812,10 @@ fn make_class_method_table( method_count: 0, }; - for class in api.classes.iter() { - let class_ty = TyName::from_godot(&class.name); - if special_cases::is_class_deleted(&class_ty) - || codegen_special_cases::is_class_excluded(&class.name) - || util::get_api_level(class) != api_level - { - continue; - } - - populate_class_methods(&mut table, class, &class_ty, ctx); - } + api.classes + .iter() + .filter(|c| c.api_level == api_level) + .for_each(|c| populate_class_methods(&mut table, c, ctx)); table.pre_init_code = quote! { let fetch_fptr = interface.classdb_get_method_bind.expect("classdb_get_method_bind absent"); @@ -959,14 +853,11 @@ fn make_named_accessors(accessors: &[AccessorMethod], fptr: &TokenStream) -> Tok result_api.append_all(code.into_iter()); } + result_api } -fn make_builtin_method_table( - api: &ExtensionApi, - builtin_types: &BuiltinTypeMap, - ctx: &mut Context, -) -> TokenStream { +fn make_builtin_method_table(api: &ExtensionApi, ctx: &mut Context) -> TokenStream { let mut table = IndexedMethodTable { table_name: ident("BuiltinMethodTable"), imports: TokenStream::new(), @@ -997,53 +888,40 @@ fn make_builtin_method_table( method_count: 0, }; - // TODO reuse builtin_types without api - for builtin in api.builtin_classes.iter() { - let Some(builtin_type) = builtin_types.map.get(&builtin.name) else { - continue; // for Nil - }; - - populate_builtin_methods(&mut table, builtin, &builtin_type.type_names, ctx); + for builtin in api.builtins.iter() { + populate_builtin_methods(&mut table, builtin, ctx); } make_method_table(table) } -fn populate_class_methods( - table: &mut IndexedMethodTable, - class: &Class, - class_ty: &TyName, - ctx: &mut Context, -) { +fn populate_class_methods(table: &mut IndexedMethodTable, class: &Class, ctx: &mut Context) { // Note: already checked outside whether class is active in codegen. - let class_var = format_ident!("sname_{}", &class.name); + let class_ty = class.name(); + let class_var = format_ident!("sname_{}", class_ty.godot_ty); let mut method_inits = vec![]; - for method in option_as_slice(&class.methods) { - if special_cases::is_class_method_deleted(class_ty, method, ctx) { + for method in class.methods.iter() { + // Virtual methods are not part of the class API itself, but exposed as an accompanying trait. + let FnDirection::Outbound { hash } = method.direction() else { continue; - } + }; // Note: varcall/ptrcall is only decided at call time; the method bind is the same for both. - let index = ctx.get_table_index(&MethodTableKey::ClassMethod { - api_level: util::get_api_level(class), - class_ty: class_ty.clone(), - method_name: method.name.clone(), - }); + let index = ctx.get_table_index(&MethodTableKey::from_class(class, method)); - let method_init = make_class_method_init(method, &class_var, class_ty); + let method_init = make_class_method_init(method, hash, &class_var, class_ty); method_inits.push(MethodInit { method_init, index }); table.method_count += 1; // If requested, add a named accessor for this method. - if special_cases::is_named_accessor_in_table(class_ty, &method.name) { + if special_cases::is_named_accessor_in_table(class_ty, method.godot_name()) { let class_name_str = class_ty.godot_ty.as_str(); - let method_name_str = method.name.as_str(); - let hash = method.hash.expect("hash present"); + let method_name_str = method.name(); table.named_accessors.push(AccessorMethod { - name: make_class_method_ptr_name(class_ty, method), + name: make_table_accessor_name(class_ty, method), index, lazy_key: quote! { crate::lazy_keys::ClassMethodKey { @@ -1070,42 +948,33 @@ fn populate_class_methods( fn populate_builtin_methods( table: &mut IndexedMethodTable, - builtin_class: &BuiltinClass, - builtin_name: &BuiltinName, + builtin: &BuiltinVariant, ctx: &mut Context, ) { - let mut method_inits = vec![]; - - // Skip types such as int, float, bool. - // TODO separate BuiltinName + TyName needed? - if special_cases::is_builtin_type_deleted(&TyName::from_godot(&builtin_class.name)) { + let Some(builtin_class) = builtin.associated_builtin_class() else { + // Ignore those where no class is generated (Object, int, bool etc.). return; - } + }; - for method in option_as_slice(&builtin_class.methods) { - let builtin_ty = TyName::from_godot(&builtin_class.name); - if special_cases::is_builtin_method_deleted(&builtin_ty, method) { - continue; - } + let builtin_ty = builtin_class.name(); - let index = ctx.get_table_index(&MethodTableKey::BuiltinMethod { - builtin_ty: builtin_ty.clone(), - method_name: method.name.clone(), - }); + let mut method_inits = vec![]; + for method in builtin_class.methods.iter() { + let index = ctx.get_table_index(&MethodTableKey::from_builtin(builtin_class, method)); - let method_init = make_builtin_method_init(method, builtin_name, index); + let method_init = make_builtin_method_init(builtin, method, index); method_inits.push(MethodInit { method_init, index }); table.method_count += 1; // If requested, add a named accessor for this method. - if special_cases::is_named_accessor_in_table(&builtin_ty, &method.name) { - let variant_type = &builtin_name.sys_variant_type; - let variant_type_str = &builtin_name.json_builtin_name; - let method_name_str = method.name.as_str(); - let hash = method.hash.expect("hash present"); + if special_cases::is_named_accessor_in_table(builtin_ty, method.godot_name()) { + let variant_type = builtin.sys_variant_type(); + let variant_type_str = builtin.godot_original_name(); + let method_name_str = method.name(); + let hash = method.hash(); table.named_accessors.push(AccessorMethod { - name: make_builtin_method_ptr_name(&builtin_ty, method), + name: make_table_accessor_name(builtin_ty, method), index, lazy_key: quote! { crate::lazy_keys::BuiltinMethodKey { @@ -1120,7 +989,7 @@ fn populate_builtin_methods( } table.method_init_groups.push(MethodInitGroup::new( - &builtin_class.name, + &builtin_class.name().godot_ty, None, // load_builtin_method() doesn't need a StringName for the class, as it accepts the VariantType enum. method_inits, )); @@ -1129,18 +998,12 @@ fn populate_builtin_methods( fn make_class_method_init( method: &ClassMethod, + hash: i64, class_var: &Ident, class_ty: &TyName, ) -> TokenStream { let class_name_str = class_ty.godot_ty.as_str(); - let method_name_str = method.name.as_str(); - - let hash = method.hash.unwrap_or_else(|| { - panic!( - "class method has no hash: {}::{}", - class_ty.godot_ty, method_name_str - ) - }); + let method_name_str = method.godot_name(); // Could reuse lazy key, but less code like this -> faster parsing. quote! { @@ -1156,21 +1019,16 @@ fn make_class_method_init( } fn make_builtin_method_init( - method: &BuiltinClassMethod, - type_name: &BuiltinName, + builtin: &BuiltinVariant, + method: &BuiltinMethod, index: usize, ) -> TokenStream { - let method_name_str = method.name.as_str(); + let method_name_str = method.name(); - let variant_type = &type_name.sys_variant_type; - let variant_type_str = &type_name.json_builtin_name; + let variant_type = builtin.sys_variant_type(); + let variant_type_str = builtin.godot_original_name(); - let hash = method.hash.unwrap_or_else(|| { - panic!( - "builtin method has no hash: {}::{}", - variant_type_str, method_name_str - ) - }); + let hash = method.hash(); // Could reuse lazy key, but less code like this -> faster parsing. quote! { @@ -1188,158 +1046,80 @@ fn make_builtin_method_init( } } -/// Creates a map from "normalized" class names (lowercase without underscore, makes it easy to map from different conventions) -/// to meta type information, including all the type name variants -fn collect_builtin_classes(api: &ExtensionApi) -> HashMap { - let mut class_map = HashMap::new(); - for class in &api.builtin_classes { - let normalized_name = class.name.to_ascii_lowercase(); - - class_map.insert(normalized_name, class); - } - - class_map -} - -/// Returns map from the JSON names (e.g. "PackedStringArray") to all the info. -pub(crate) fn collect_builtin_types(api: &ExtensionApi) -> HashMap> { - let class_map = collect_builtin_classes(api); - - let variant_type_enum = api - .global_enums - .iter() - .find(|e| &e.name == "Variant.Type") - .expect("missing enum for VariantType in JSON"); - - // Collect all `BuiltinTypeInfo`s - let mut builtin_types_map = HashMap::new(); - for ty in &variant_type_enum.values { - let shout_case = ty - .name - .strip_prefix("TYPE_") - .expect("enum name begins with 'TYPE_'"); - - if shout_case == "NIL" || shout_case == "MAX" { - continue; - } - - // Lowercase without underscore, to map SHOUTY_CASE to shoutycase - let normalized = shout_case.to_ascii_lowercase().replace('_', ""); - - // TODO cut down on the number of cached functions generated - // e.g. there's no point in providing operator< for int - let class_name: String; - let has_destructor: bool; - let constructors: Option<&Vec>; - let operators: Option<&Vec>; - if let Some(class) = class_map.get(&normalized) { - class_name = class.name.clone(); - has_destructor = class.has_destructor; - constructors = Some(&class.constructors); - operators = Some(&class.operators); - } else { - assert_eq!(normalized, "object"); - class_name = "Object".to_string(); - has_destructor = false; - constructors = None; - operators = None; - } - - let type_names = BuiltinName { - json_builtin_name: class_name.clone(), - snake_case: conv::to_snake_case(&class_name), - //shout_case: shout_case.to_string(), - sys_variant_type: format_ident!("GDEXTENSION_VARIANT_TYPE_{}", shout_case), - }; - - let variant_type_ord = ty.to_enum_ord(); - builtin_types_map.insert( - type_names.json_builtin_name.clone(), - BuiltinTypeInfo { - variant_type_ord, - type_names, - has_destructor, - constructors, - operators, - }, - ); - } - builtin_types_map -} - -fn collect_variant_operators(api: &ExtensionApi) -> Vec<&EnumConstant> { +fn collect_variant_operators(api: &ExtensionApi) -> Vec<&Enumerator> { let variant_operator_enum = api .global_enums .iter() - .find(|e| &e.name == "Variant.Operator") + .find(|e| &e.name == "VariantOperator") // in JSON: "Variant.Operator" .expect("missing enum for VariantOperator in JSON"); - variant_operator_enum.values.iter().collect() -} - -/// Only used for `VariantType` enum at the moment. -/// -/// Returns **unsuffixed** ordinals, to allow for different platform types of the C definition. Adjust if suffixed is desired, for type safety. -fn make_enumerator( - type_names: &BuiltinName, - value: i32, - ctx: &mut Context, -) -> (Ident, TokenStream, Literal) { - let enumerator_name = &type_names.json_builtin_name; - let pascal_name = conv::to_pascal_case(enumerator_name); - let rust_ty = conv::to_rust_type(enumerator_name, None, ctx); - let ord = util::make_enumerator_ord_unsuffixed(value); - - (ident(&pascal_name), rust_ty.to_token_stream(), ord) + variant_operator_enum.enumerators.iter().collect() } -fn make_opaque_type(name: &str, size: usize) -> TokenStream { - let name = conv::to_pascal_case(name); +fn make_opaque_type(godot_original_name: &str, size: usize) -> TokenStream { + let name = conv::to_pascal_case(godot_original_name); let (first, rest) = name.split_at(1); - // Capitalize: "int" -> "Int" + // Capitalize: "int" -> "Int". let ident = format_ident!("Opaque{}{}", first.to_ascii_uppercase(), rest); quote! { pub type #ident = crate::opaque::Opaque<#size>; } } -fn make_variant_fns( - type_names: &BuiltinName, - has_destructor: bool, - constructors: Option<&Vec>, - operators: Option<&Vec>, - builtin_types: &HashMap, -) -> (TokenStream, TokenStream) { - let (construct_decls, construct_inits) = - make_construct_fns(type_names, constructors, builtin_types); - let (destroy_decls, destroy_inits) = make_destroy_fns(type_names, has_destructor); - let (op_eq_decls, op_eq_inits) = make_operator_fns(type_names, operators, "==", "EQUAL"); - let (op_lt_decls, op_lt_inits) = make_operator_fns(type_names, operators, "<", "LESS"); +fn make_variant_fns(api: &ExtensionApi, builtin: &BuiltinVariant) -> (TokenStream, TokenStream) { + let (special_decls, special_inits); + if let Some(builtin_class) = builtin.associated_builtin_class() { + let (construct_decls, construct_inits) = + make_construct_fns(api, builtin, &builtin_class.constructors); + + let (destroy_decls, destroy_inits) = + make_destroy_fns(builtin, builtin_class.has_destructor); + + let (op_eq_decls, op_eq_inits) = + make_operator_fns(builtin, &builtin_class.operators, "==", "EQUAL"); + + let (op_lt_decls, op_lt_inits) = + make_operator_fns(builtin, &builtin_class.operators, "<", "LESS"); + + special_decls = quote! { + #op_eq_decls + #op_lt_decls + #construct_decls + #destroy_decls + }; + special_inits = quote! { + #op_eq_inits + #op_lt_inits + #construct_inits + #destroy_inits + }; + } else { + special_decls = TokenStream::new(); + special_inits = TokenStream::new(); + }; - let to_variant = format_ident!("{}_to_variant", type_names.snake_case); - let from_variant = format_ident!("{}_from_variant", type_names.snake_case); + let snake_case = builtin.snake_name(); + let to_variant = format_ident!("{}_to_variant", snake_case); + let from_variant = format_ident!("{}_from_variant", snake_case); let to_variant_str = to_variant.to_string(); let from_variant_str = from_variant.to_string(); - let variant_type = &type_names.sys_variant_type; + let variant_type = builtin.sys_variant_type(); let variant_type = quote! { crate::#variant_type }; - // Field declaration + // Field declaration. // The target types are uninitialized-ptrs, because Godot performs placement new on those: // https://github.com/godotengine/godot/blob/b40b35fb39f0d0768d7ec2976135adffdce1b96d/core/variant/variant_internal.h#L1535-L1535 let decl = quote! { pub #to_variant: unsafe extern "C" fn(GDExtensionUninitializedVariantPtr, GDExtensionTypePtr), pub #from_variant: unsafe extern "C" fn(GDExtensionUninitializedTypePtr, GDExtensionVariantPtr), - #op_eq_decls - #op_lt_decls - #construct_decls - #destroy_decls + #special_decls }; - // Field initialization in new() + // Field initialization in new(). let init = quote! { #to_variant: { let fptr = unsafe { get_to_variant_fn(#variant_type) }; @@ -1349,28 +1129,20 @@ fn make_variant_fns( let fptr = unsafe { get_from_variant_fn(#variant_type) }; crate::validate_builtin_lifecycle(fptr, #from_variant_str) }, - #op_eq_inits - #op_lt_inits - #construct_inits - #destroy_inits + #special_inits }; (decl, init) } fn make_construct_fns( - type_names: &BuiltinName, - constructors: Option<&Vec>, - builtin_types: &HashMap, + api: &ExtensionApi, + builtin: &BuiltinVariant, + constructors: &[Constructor], ) -> (TokenStream, TokenStream) { - let constructors = match constructors { - Some(c) => c, - None => return (TokenStream::new(), TokenStream::new()), - }; - - if is_trivial(type_names) { + if constructors.is_empty() { return (TokenStream::new(), TokenStream::new()); - } + }; // Constructor vec layout: // [0]: default constructor @@ -1378,32 +1150,31 @@ fn make_construct_fns( // [2]: (optional) typically the most common conversion constructor (e.g. StringName -> String) // rest: (optional) other conversion constructors and multi-arg constructors (e.g. Vector3(x, y, z)) - // Sanity checks -- ensure format is as expected + // Sanity checks -- ensure format is as expected. for (i, c) in constructors.iter().enumerate() { assert_eq!(i, c.index); } - assert!(constructors[0].arguments.is_none()); + assert!( + constructors[0].raw_parameters.is_empty(), + "default constructor at index 0 must have no parameters" + ); - if let Some(args) = &constructors[1].arguments { - assert_eq!(args.len(), 1); - assert_eq!(args[0].name, "from"); - assert_eq!(args[0].type_, type_names.json_builtin_name); - } else { - panic!( - "type {}: no constructor args found for copy constructor", - type_names.json_builtin_name - ); - } + let args = &constructors[1].raw_parameters; + assert_eq!(args.len(), 1); + assert_eq!(args[0].name, "from"); + assert_eq!(args[0].type_, builtin.godot_original_name()); - let construct_default = format_ident!("{}_construct_default", type_names.snake_case); - let construct_copy = format_ident!("{}_construct_copy", type_names.snake_case); + let builtin_snake_name = builtin.snake_name(); + let variant_type = builtin.sys_variant_type(); + + let construct_default = format_ident!("{builtin_snake_name}_construct_default"); + let construct_copy = format_ident!("{builtin_snake_name}_construct_copy"); let construct_default_str = construct_default.to_string(); let construct_copy_str = construct_copy.to_string(); - let variant_type = &type_names.sys_variant_type; let (construct_extra_decls, construct_extra_inits) = - make_extra_constructors(type_names, constructors, builtin_types); + make_extra_constructors(api, builtin, constructors); // Target types are uninitialized pointers, because Godot uses placement-new for raw pointer constructions. Callstack: // https://github.com/godotengine/godot/blob/b40b35fb39f0d0768d7ec2976135adffdce1b96d/core/extension/gdextension_interface.cpp#L511 @@ -1438,58 +1209,63 @@ fn make_construct_fns( /// Lists special cases for useful constructors fn make_extra_constructors( - type_names: &BuiltinName, - constructors: &Vec, - builtin_types: &HashMap, + api: &ExtensionApi, + builtin: &BuiltinVariant, + constructors: &[Constructor], ) -> (Vec, Vec) { let mut extra_decls = Vec::with_capacity(constructors.len() - 2); let mut extra_inits = Vec::with_capacity(constructors.len() - 2); - let variant_type = &type_names.sys_variant_type; + let variant_type = builtin.sys_variant_type(); for (i, ctor) in constructors.iter().enumerate().skip(2) { - if let Some(args) = &ctor.arguments { - let type_name = &type_names.snake_case; - let construct_custom = if args.len() == 1 && args[0].name == "from" { - // Conversion constructor is named according to the source type - // String(NodePath from) => string_from_node_path - let arg_type = &builtin_types[&args[0].type_].type_names.snake_case; - format_ident!("{type_name}_from_{arg_type}") - } else { - // Type-specific constructor is named according to the argument names - // Vector3(float x, float y, float z) => vector3_from_x_y_z - let mut arg_names = args - .iter() - .fold(String::new(), |acc, arg| acc + &arg.name + "_"); - arg_names.pop(); // remove trailing '_' - format_ident!("{type_name}_from_{arg_names}") - }; + let args = &ctor.raw_parameters; + assert!( + !args.is_empty(), + "custom constructors must have at least 1 parameter" + ); + + let type_name = builtin.snake_name(); + let construct_custom = if args.len() == 1 && args[0].name == "from" { + // Conversion constructor is named according to the source type: + // String(NodePath from) => string_from_node_path + + let arg_type = api.builtin_by_original_name(&args[0].type_).snake_name(); + format_ident!("{type_name}_from_{arg_type}") + } else { + // Type-specific constructor is named according to the argument names: + // Vector3(float x, float y, float z) => vector3_from_x_y_z + let mut arg_names = args + .iter() + .fold(String::new(), |acc, arg| acc + &arg.name + "_"); + arg_names.pop(); // remove trailing '_' + format_ident!("{type_name}_from_{arg_names}") + }; - let construct_custom_str = construct_custom.to_string(); - extra_decls.push(quote! { + let construct_custom_str = construct_custom.to_string(); + extra_decls.push(quote! { pub #construct_custom: unsafe extern "C" fn(GDExtensionUninitializedTypePtr, *const GDExtensionConstTypePtr), }); - let i = i as i32; - extra_inits.push(quote! { - #construct_custom: { - let fptr = unsafe { get_construct_fn(crate::#variant_type, #i) }; - crate::validate_builtin_lifecycle(fptr, #construct_custom_str) - }, - }); - } + let i = i as i32; + extra_inits.push(quote! { + #construct_custom: { + let fptr = unsafe { get_construct_fn(crate::#variant_type, #i) }; + crate::validate_builtin_lifecycle(fptr, #construct_custom_str) + }, + }); } (extra_decls, extra_inits) } -fn make_destroy_fns(type_names: &BuiltinName, has_destructor: bool) -> (TokenStream, TokenStream) { - if !has_destructor || is_trivial(type_names) { +fn make_destroy_fns(builtin: &BuiltinVariant, has_destructor: bool) -> (TokenStream, TokenStream) { + if !has_destructor { return (TokenStream::new(), TokenStream::new()); } - let destroy = format_ident!("{}_destroy", type_names.snake_case); + let destroy = format_ident!("{}_destroy", builtin.snake_name()); let destroy_str = destroy.to_string(); - let variant_type = &type_names.sys_variant_type; + let variant_type = builtin.sys_variant_type(); let decls = quote! { pub #destroy: unsafe extern "C" fn(GDExtensionTypePtr), @@ -1506,35 +1282,33 @@ fn make_destroy_fns(type_names: &BuiltinName, has_destructor: bool) -> (TokenStr } fn make_operator_fns( - type_names: &BuiltinName, - operators: Option<&Vec>, - json_name: &str, + builtin: &BuiltinVariant, + operators: &[Operator], + json_symbol: &str, sys_name: &str, ) -> (TokenStream, TokenStream) { - if operators.is_none() - || !operators.unwrap().iter().any(|op| op.name == json_name) - || is_trivial(type_names) - { + // If there are no operators for that builtin type, or none of the operator matches symbol, then don't generate function. + if operators.is_empty() || !operators.iter().any(|op| op.symbol == json_symbol) { return (TokenStream::new(), TokenStream::new()); } let operator = format_ident!( "{}_operator_{}", - type_names.snake_case, + builtin.snake_name(), sys_name.to_ascii_lowercase() ); let operator_str = operator.to_string(); - let variant_type = &type_names.sys_variant_type; + let variant_type = builtin.sys_variant_type(); let variant_type = quote! { crate::#variant_type }; let sys_ident = format_ident!("GDEXTENSION_VARIANT_OP_{}", sys_name); - // Field declaration + // Field declaration. let decl = quote! { pub #operator: unsafe extern "C" fn(GDExtensionConstTypePtr, GDExtensionConstTypePtr, GDExtensionTypePtr), }; - // Field initialization in new() + // Field initialization in new(). let init = quote! { #operator: { let fptr = unsafe { get_operator_fn(crate::#sys_ident, #variant_type, #variant_type) }; @@ -1544,11 +1318,3 @@ fn make_operator_fns( (decl, init) } - -/// Returns true if the type is so trivial that most of its operations are directly provided by Rust, and there is no need -/// to expose the construct/destruct/operator methods from Godot -fn is_trivial(type_names: &BuiltinName) -> bool { - let list = ["bool", "int", "float"]; - - list.contains(&type_names.json_builtin_name.as_str()) -} diff --git a/godot-codegen/src/class_generator.rs b/godot-codegen/src/class_generator.rs index 7e623eb69..d9e1e683b 100644 --- a/godot-codegen/src/class_generator.rs +++ b/godot-codegen/src/class_generator.rs @@ -11,17 +11,16 @@ use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use std::path::Path; -use crate::api_parser::*; -use crate::central_generator::collect_builtin_types; use crate::context::NotificationEnum; +use crate::models::domain::BuiltinMethod; +use crate::models::domain::*; use crate::util::{ ident, make_string_name, option_as_slice, parse_native_structures_format, safe_ident, - ClassCodegenLevel, MethodTableKey, NativeStructuresField, + MethodTableKey, NativeStructuresField, }; use crate::{ - codegen_special_cases, conv, special_cases, util, Context, GeneratedBuiltin, - GeneratedBuiltinModule, GeneratedClass, GeneratedClassModule, ModName, RustTy, SubmitFn, - TyName, + conv, special_cases, util, Context, GeneratedBuiltin, GeneratedBuiltinModule, GeneratedClass, + GeneratedClassModule, ModName, SubmitFn, }; // ---------------------------------------------------------------------------------------------------------------------------------------------- @@ -34,7 +33,6 @@ struct FnReceiver { ffi_arg: TokenStream, /// `Self::`, `self.` - #[allow(dead_code)] // TODO remove as soon as used self_prefix: TokenStream, } @@ -51,136 +49,6 @@ impl FnReceiver { // ---------------------------------------------------------------------------------------------------------------------------------------------- -struct FnParam { - name: Ident, - type_: RustTy, - default_value: Option, -} - -impl FnParam { - fn new_range(method_args: &Option>, ctx: &mut Context) -> Vec { - option_as_slice(method_args) - .iter() - .map(|arg| Self::new(arg, ctx)) - .collect() - } - - fn new_range_no_defaults( - method_args: &Option>, - ctx: &mut Context, - ) -> Vec { - option_as_slice(method_args) - .iter() - .map(|arg| Self::new_no_defaults(arg, ctx)) - .collect() - } - - fn new(method_arg: &MethodArg, ctx: &mut Context) -> FnParam { - let name = safe_ident(&method_arg.name); - let type_ = conv::to_rust_type(&method_arg.type_, method_arg.meta.as_ref(), ctx); - let default_value = method_arg - .default_value - .as_ref() - .map(|v| conv::to_rust_expr(v, &type_)); - - FnParam { - name, - type_, - default_value, - } - } - - fn new_no_defaults(method_arg: &MethodArg, ctx: &mut Context) -> FnParam { - FnParam { - name: safe_ident(&method_arg.name), - type_: conv::to_rust_type(&method_arg.type_, method_arg.meta.as_ref(), ctx), - //type_: to_rust_type(&method_arg.type_, &method_arg.meta, ctx), - default_value: None, - } - } -} - -// ---------------------------------------------------------------------------------------------------------------------------------------------- - -struct FnReturn { - decl: TokenStream, - type_: Option, -} - -impl FnReturn { - fn new(return_value: &Option, ctx: &mut Context) -> Self { - if let Some(ret) = return_value { - let ty = conv::to_rust_type(&ret.type_, ret.meta.as_ref(), ctx); - - Self { - decl: ty.return_decl(), - type_: Some(ty), - } - } else { - Self { - decl: TokenStream::new(), - type_: None, - } - } - } - - fn type_tokens(&self) -> TokenStream { - match &self.type_ { - Some(RustTy::EngineClass { tokens, .. }) => { - quote! { Option<#tokens> } - } - Some(ty) => { - quote! { #ty } - } - _ => { - quote! { () } - } - } - } -} - -// ---------------------------------------------------------------------------------------------------------------------------------------------- - -enum FnQualifier { - Mut, // &mut self - Const, // &self - Static, // Self - Global, // (nothing) -} - -impl FnQualifier { - fn is_static_or_global(&self) -> bool { - matches!(self, Self::Static | Self::Global) - } -} - -impl FnQualifier { - fn for_method(is_const: bool, is_static: bool) -> FnQualifier { - if is_static { - FnQualifier::Static - } else if is_const { - FnQualifier::Const - } else { - FnQualifier::Mut - } - } -} - -// ---------------------------------------------------------------------------------------------------------------------------------------------- - -struct FnSignature<'a> { - function_name: &'a str, - surrounding_class: Option<&'a TyName>, // None if global function - is_private: bool, - is_virtual: bool, - is_vararg: bool, - qualifier: FnQualifier, - params: Vec, - return_value: FnReturn, -} - -// ---------------------------------------------------------------------------------------------------------------------------------------------- - struct FnCode { receiver: FnReceiver, varcall_invocation: TokenStream, @@ -239,7 +107,6 @@ impl FnDefinitions { pub(crate) fn generate_class_files( api: &ExtensionApi, ctx: &mut Context, - _build_config: [&str; 2], gen_path: &Path, submit_fn: &mut SubmitFn, ) { @@ -248,25 +115,16 @@ pub(crate) fn generate_class_files( let mut modules = vec![]; for class in api.classes.iter() { - let class_name = TyName::from_godot(&class.name); - let module_name = ModName::from_godot(&class.name); - - if special_cases::is_class_deleted(&class_name) - || codegen_special_cases::is_class_excluded(class_name.godot_ty.as_str()) - { - continue; - } - - let generated_class = make_class(class, &class_name, ctx); + let generated_class = make_class(class, ctx); let file_contents = generated_class.code; - let out_path = gen_path.join(format!("{}.rs", module_name.rust_mod)); + let out_path = gen_path.join(format!("{}.rs", class.mod_name().rust_mod)); submit_fn(out_path, file_contents); modules.push(GeneratedClassModule { - class_name, - module_name, + class_name: class.name().clone(), + module_name: class.mod_name().clone(), own_notification_enum_name: generated_class.notification_enum.try_to_own_name(), inherits_macro_ident: generated_class.inherits_macro_ident, is_pub_sidecar: generated_class.has_sidecar_module, @@ -282,30 +140,22 @@ pub(crate) fn generate_class_files( pub(crate) fn generate_builtin_class_files( api: &ExtensionApi, ctx: &mut Context, - _build_config: [&str; 2], gen_path: &Path, submit_fn: &mut SubmitFn, ) { let _ = std::fs::remove_dir_all(gen_path); std::fs::create_dir_all(gen_path).expect("create classes directory"); - let builtin_types_map = collect_builtin_types(api); - let mut modules = vec![]; - for class in api.builtin_classes.iter() { - let module_name = ModName::from_godot(&class.name); - let builtin_name = TyName::from_godot(&class.name); - let inner_builtin_name = TyName::from_godot(&format!("Inner{}", class.name)); - - if special_cases::is_builtin_type_deleted(&builtin_name) { + for class in api.builtins.iter() { + let Some(class) = class.builtin_class.as_ref() else { continue; - } + }; - let _type_info = builtin_types_map - .get(&class.name) - .unwrap_or_else(|| panic!("builtin type not found: {}", class.name)); + // let godot_class_name = &class.name().godot_ty; + let module_name = class.mod_name(); - let generated_class = make_builtin_class(class, &builtin_name, &inner_builtin_name, ctx); + let generated_class = make_builtin_class(class, ctx); let file_contents = generated_class.code; let out_path = gen_path.join(format!("{}.rs", module_name.rust_mod)); @@ -313,8 +163,8 @@ pub(crate) fn generate_builtin_class_files( submit_fn(out_path, file_contents); modules.push(GeneratedBuiltinModule { - class_name: inner_builtin_name, - module_name, + symbol_ident: class.inner_name().clone(), + module_name: module_name.clone(), }); } @@ -327,7 +177,6 @@ pub(crate) fn generate_builtin_class_files( pub(crate) fn generate_native_structures_files( api: &ExtensionApi, ctx: &mut Context, - _build_config: [&str; 2], gen_path: &Path, submit_fn: &mut SubmitFn, ) { @@ -347,7 +196,7 @@ pub(crate) fn generate_native_structures_files( submit_fn(out_path, file_contents); modules.push(GeneratedBuiltinModule { - class_name, + symbol_ident: class_name.rust_ty.clone(), module_name, }); } @@ -442,12 +291,8 @@ fn make_module_doc(class_name: &TyName) -> String { ) } -fn make_constructor_and_default( - class: &Class, - class_name: &TyName, - ctx: &Context, -) -> (TokenStream, TokenStream) { - let godot_class_name = &class.name; +fn make_constructor_and_default(class: &Class, ctx: &Context) -> (TokenStream, TokenStream) { + let godot_class_name = &class.name().godot_ty; let godot_class_stringname = make_string_name(godot_class_name); // Note: this could use class_name() but is not yet done due to upcoming lazy-load refactoring. //let class_name_obj = quote! { ::class_name() }; @@ -490,6 +335,7 @@ fn make_constructor_and_default( } let godot_default_impl = if has_godot_default_impl { + let class_name = &class.name().rust_ty; quote! { impl crate::obj::cap::GodotDefault for #class_name { fn __godot_default() -> crate::obj::Gd { @@ -504,7 +350,9 @@ fn make_constructor_and_default( (constructor, godot_default_impl) } -fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> GeneratedClass { +fn make_class(class: &Class, ctx: &mut Context) -> GeneratedClass { + let class_name = class.name(); + // Strings let godot_class_str = &class_name.godot_ty; let class_name_cstr = util::cstr_u8_slice(godot_class_str); @@ -519,17 +367,18 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate None => (quote! { () }, None), }; - let (constructor, godot_default_impl) = make_constructor_and_default(class, class_name, ctx); - let api_level = util::get_api_level(class); + let (constructor, godot_default_impl) = make_constructor_and_default(class, ctx); + let api_level = class.api_level; let init_level = api_level.to_init_level(); let FnDefinitions { functions: methods, builders, - } = make_methods(option_as_slice(&class.methods), class_name, &api_level, ctx); + } = make_methods(class, &class.methods, ctx); + + let enums = make_enums(&class.enums); - let enums = make_enums(option_as_slice(&class.enums), class_name, ctx); - let constants = make_constants(option_as_slice(&class.constants), class_name, ctx); + let constants = make_constants(&class.constants); let inherits_macro = format_ident!("inherits_transitive_{}", class_name.rust_ty); let (exportable_impl, exportable_macro_impl) = if ctx.is_exportable(class_name) { @@ -585,7 +434,6 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate let module_doc = make_module_doc(class_name); let virtual_trait = make_virtual_methods_trait( class, - class_name, &all_bases, &virtual_trait_str, ¬ification_enum_name, @@ -847,40 +695,22 @@ fn workaround_constant_collision(all_constants: &mut Vec<(Ident, i32)>) { } } -fn make_builtin_class( - class: &BuiltinClass, - builtin_name: &TyName, - inner_class_name: &TyName, - ctx: &mut Context, -) -> GeneratedBuiltin { - let outer_class = - if let RustTy::BuiltinIdent(ident) = conv::to_rust_type(&class.name, None, ctx) { - ident - } else { - panic!("Rust type `{}` categorized wrong", class.name) - }; - let inner_class = &inner_class_name.rust_ty; +fn make_builtin_class(class: &BuiltinClass, ctx: &mut Context) -> GeneratedBuiltin { + let godot_name = &class.name().godot_ty; - let class_enums = class.enums.as_ref().map_or(Vec::new(), |class_enums| { - class_enums - .iter() - .map(BuiltinClassEnum::to_enum) - .collect::>() - }); + let RustTy::BuiltinIdent(outer_class) = conv::to_rust_type(godot_name, None, ctx) else { + panic!("Rust type `{}` categorized wrong", godot_name) + }; + let inner_class = class.inner_name(); let FnDefinitions { functions: methods, builders, - } = make_builtin_methods( - option_as_slice(&class.methods), - builtin_name, - inner_class_name, - ctx, - ); + } = make_builtin_methods(class, &class.methods, ctx); let imports = util::make_imports(); - let enums = make_enums(&class_enums, builtin_name, ctx); - let special_constructors = make_special_builtin_methods(builtin_name, ctx); + let enums = make_enums(&class.enums); + let special_constructors = make_special_builtin_methods(class.name(), ctx); // mod re_export needed, because class should not appear inside the file module, and we can't re-export private struct as pub let code = quote! { @@ -1070,13 +900,13 @@ fn make_builtin_module_file(classes_and_modules: Vec) -> let decls = classes_and_modules.iter().map(|m| { let GeneratedBuiltinModule { module_name, - class_name, + symbol_ident, .. } = m; quote! { mod #module_name; - pub use #module_name::#class_name; + pub use #module_name::#symbol_ident; } }); @@ -1085,49 +915,37 @@ fn make_builtin_module_file(classes_and_modules: Vec) -> } } -fn make_methods( - methods: &[ClassMethod], - class_name: &TyName, - api_level: &ClassCodegenLevel, - ctx: &mut Context, -) -> FnDefinitions { - let get_method_table = api_level.table_global_getter(); +fn make_methods(class: &Class, methods: &[ClassMethod], ctx: &mut Context) -> FnDefinitions { + let get_method_table = class.api_level.table_global_getter(); - let definitions = methods.iter().map(|method| { - make_class_method_definition(method, class_name, api_level, &get_method_table, ctx) - }); + let definitions = methods + .iter() + .map(|method| make_class_method_definition(class, method, &get_method_table, ctx)); FnDefinitions::expand(definitions) } fn make_builtin_methods( - methods: &[BuiltinClassMethod], - builtin_name: &TyName, - inner_class_name: &TyName, + builtin_class: &BuiltinClass, + methods: &[BuiltinMethod], ctx: &mut Context, ) -> FnDefinitions { let definitions = methods .iter() - .map(|method| make_builtin_method_definition(method, builtin_name, inner_class_name, ctx)); + .map(|method| make_builtin_method_definition(builtin_class, method, ctx)); FnDefinitions::expand(definitions) } -fn make_enums(enums: &[Enum], class_name: &TyName, _ctx: &Context) -> TokenStream { - let definitions = enums - .iter() - .map(|e| util::make_enum_definition(e, Some(&class_name.godot_ty))); +fn make_enums(enums: &[Enum]) -> TokenStream { + let definitions = enums.iter().map(util::make_enum_definition); quote! { #( #definitions )* } } -fn make_constants( - constants: &[ClassConstant], - _class_name: &TyName, - _ctx: &Context, -) -> TokenStream { +fn make_constants(constants: &[ClassConstant]) -> TokenStream { let definitions = constants.iter().map(util::make_constant_definition); quote! { @@ -1155,58 +973,30 @@ fn make_special_builtin_methods(class_name: &TyName, _ctx: &Context) -> TokenStr } fn make_class_method_definition( + class: &Class, method: &ClassMethod, - class_name: &TyName, - api_level: &ClassCodegenLevel, get_method_table: &Ident, ctx: &mut Context, ) -> FnDefinition { - if special_cases::is_class_method_deleted(class_name, method, ctx) { + let FnDirection::Outbound { hash } = method.direction() else { return FnDefinition::none(); - } - - let class_name_str = &class_name.godot_ty; - let godot_method_name = &method.name; - let rust_method_name = special_cases::maybe_rename_class_method(class_name, godot_method_name); - - // Override const-qualification for known special cases (FileAccess::get_16, StreamPeer::get_u16, etc.). - let mut is_actually_const = method.is_const; - if let Some(override_const) = special_cases::is_class_method_const(class_name, method) { - is_actually_const = override_const; - } + }; - /* - // TODO re-enable this once JSON/domain models are separated. - // Getters in particular are re-qualified as const (if there isn't already an override). - if override_is_const.is_none() && option_as_slice(&method.arguments).is_empty() { - if rust_method_name.starts_with("get_") { - // Many getters are mutably qualified (GltfAccessor::get_max, CameraAttributes::get_exposure_multiplier, ...). - // As a default, set those to const. - override_is_const = Some(true); - } - }*/ + let rust_method_name = method.name(); + let godot_method_name = method.godot_name(); - let receiver = make_receiver( - method.is_static, - //override_is_const.unwrap_or(method.is_const), - is_actually_const, - quote! { self.object_ptr }, - ); + let receiver = make_receiver(method.qualifier(), quote! { self.object_ptr }); - let table_index = ctx.get_table_index(&MethodTableKey::ClassMethod { - api_level: *api_level, - class_ty: class_name.clone(), - method_name: method.name.clone(), - }); + let table_index = ctx.get_table_index(&MethodTableKey::from_class(class, method)); - let maybe_instance_id = if method.is_static { + let maybe_instance_id = if method.qualifier() == FnQualifier::Static { quote! { None } } else { quote! { self.__checked_id() } }; let fptr_access = if cfg!(feature = "codegen-lazy-fptrs") { - let hash = method.hash.expect("hash present for class method"); + let class_name_str = &class.name().godot_ty; quote! { fptr_by_key(sys::lazy_keys::ClassMethodKey { class_name: #class_name_str, @@ -1245,16 +1035,7 @@ fn make_class_method_definition( }; make_function_definition( - &FnSignature { - function_name: rust_method_name, - surrounding_class: Some(class_name), - is_private: special_cases::is_method_private(class_name, godot_method_name), - is_virtual: false, - is_vararg: method.is_vararg, - qualifier: FnQualifier::for_method(method.is_const, method.is_static), - params: FnParam::new_range(&method.arguments, ctx), - return_value: FnReturn::new(&method.return_value, ctx), - }, + method, &FnCode { receiver, varcall_invocation, @@ -1264,26 +1045,20 @@ fn make_class_method_definition( } fn make_builtin_method_definition( - method: &BuiltinClassMethod, - builtin_name: &TyName, - inner_class_name: &TyName, + builtin_class: &BuiltinClass, + method: &BuiltinMethod, ctx: &mut Context, ) -> FnDefinition { - if special_cases::is_builtin_method_deleted(builtin_name, method) { - return FnDefinition::none(); - } - - let method_name_str = &method.name; + let FnDirection::Outbound { hash } = method.direction() else { + unreachable!("builtin methods are never virtual") + }; - let return_value = method - .return_type - .as_deref() - .map(MethodReturn::from_type_no_meta); + let builtin_name = builtin_class.name(); + let method_name_str = method.godot_name(); let fptr_access = if cfg!(feature = "codegen-lazy-fptrs") { let variant_type = quote! { sys::VariantType::#builtin_name }; let variant_type_str = &builtin_name.godot_ty; - let hash = method.hash.expect("hash present for class method"); quote! { fptr_by_key(sys::lazy_keys::BuiltinMethodKey { @@ -1294,14 +1069,12 @@ fn make_builtin_method_definition( }) } } else { - let table_index = ctx.get_table_index(&MethodTableKey::BuiltinMethod { - builtin_ty: builtin_name.clone(), - method_name: method.name.clone(), - }); + let table_index = ctx.get_table_index(&MethodTableKey::from_builtin(builtin_class, method)); + quote! { fptr_by_index(#table_index) } }; - let receiver = make_receiver(method.is_static, method.is_const, quote! { self.sys_ptr }); + let receiver = make_receiver(method.qualifier(), quote! { self.sys_ptr }); let object_ptr = &receiver.ffi_arg; let ptrcall_invocation = quote! { @@ -1327,19 +1100,7 @@ fn make_builtin_method_definition( }; make_function_definition( - &FnSignature { - function_name: method_name_str, - surrounding_class: Some(inner_class_name), - is_private: special_cases::is_method_private(builtin_name, &method.name), - is_virtual: false, - is_vararg: method.is_vararg, - qualifier: FnQualifier::for_method(method.is_const, method.is_static), - - // Disable default parameters for builtin classes. - // They are not public-facing and need more involved implementation (lifetimes etc). Also reduces number of symbols in API. - params: FnParam::new_range_no_defaults(&method.arguments, ctx), - return_value: FnReturn::new(&return_value, ctx), - }, + method, &FnCode { receiver, varcall_invocation, @@ -1348,21 +1109,9 @@ fn make_builtin_method_definition( ) } -pub(crate) fn make_utility_function_definition( - function: &UtilityFunction, - ctx: &mut Context, -) -> TokenStream { - if codegen_special_cases::is_function_excluded(function, ctx) { - return TokenStream::new(); - } - - let function_name_str = &function.name; - let fn_ptr = util::make_utility_function_ptr_name(function); - - let return_value = function - .return_type - .as_deref() - .map(MethodReturn::from_type_no_meta); +pub(crate) fn make_utility_function_definition(function: &UtilityFunction) -> TokenStream { + let function_name_str = function.name(); + let fn_ptr = util::make_utility_function_ptr_name(function_name_str); let ptrcall_invocation = quote! { let utility_fn = sys::utility_function_table().#fn_ptr; @@ -1386,16 +1135,7 @@ pub(crate) fn make_utility_function_definition( }; let definition = make_function_definition( - &FnSignature { - function_name: function_name_str, - surrounding_class: None, - is_private: false, - is_virtual: false, - is_vararg: function.is_vararg, - qualifier: FnQualifier::Global, - params: FnParam::new_range(&function.arguments, ctx), - return_value: FnReturn::new(&return_value, ctx), - }, + function, &FnCode { receiver: FnReceiver::global_function(), varcall_invocation, @@ -1415,14 +1155,14 @@ fn make_vis(is_private: bool) -> TokenStream { } } -fn make_function_definition(sig: &FnSignature, code: &FnCode) -> FnDefinition { +fn make_function_definition(sig: &dyn Function, code: &FnCode) -> FnDefinition { let has_default_params = function_uses_default_params(sig); let vis = if has_default_params { // Public API mapped by separate function. // Needs to be crate-public because default-arg builder lives outside of the module. quote! { pub(crate) } } else { - make_vis(sig.is_private) + make_vis(sig.is_private()) }; let (maybe_unsafe, safety_doc) = if function_uses_pointers(sig) { @@ -1438,13 +1178,13 @@ fn make_function_definition(sig: &FnSignature, code: &FnCode) -> FnDefinition { (TokenStream::new(), TokenStream::new()) }; - let [params, param_types, arg_names] = make_params_exprs(&sig.params); + let [params, param_types, arg_names] = make_params_exprs(sig.params()); - let godot_fn_name_str = sig.function_name; + let rust_function_name_str = sig.name(); let primary_fn_name = if has_default_params { - format_ident!("{}_full", safe_ident(godot_fn_name_str)) + format_ident!("{}_full", safe_ident(rust_function_name_str)) } else { - safe_ident(godot_fn_name_str) + safe_ident(rust_function_name_str) }; let (default_fn_code, default_structs_code) = if has_default_params { @@ -1453,15 +1193,15 @@ fn make_function_definition(sig: &FnSignature, code: &FnCode) -> FnDefinition { (TokenStream::new(), TokenStream::new()) }; - let return_ty = &sig.return_value.type_tokens(); + let return_ty = &sig.return_value().type_tokens(); let call_sig = quote! { ( #return_ty, #(#param_types),* ) }; - let return_decl = &sig.return_value.decl; + let return_decl = &sig.return_value().decl; let receiver_param = &code.receiver.param; - let primary_function = if sig.is_virtual { + let primary_function = if sig.is_virtual() { // Virtual functions quote! { @@ -1473,7 +1213,7 @@ fn make_function_definition(sig: &FnSignature, code: &FnCode) -> FnDefinition { unimplemented!() } } - } else if sig.is_vararg { + } else if sig.is_vararg() { // Varargs (usually varcall, but not necessarily -- utilities use ptrcall) // If the return type is not Variant, then convert to concrete target type @@ -1500,7 +1240,7 @@ fn make_function_definition(sig: &FnSignature, code: &FnCode) -> FnDefinition { // Always ptrcall, no varargs let ptrcall_invocation = &code.ptrcall_invocation; - let maybe_return_ty = &sig.return_value.type_; + let maybe_return_ty = &sig.return_value().type_; // This differentiation is needed because we need to differentiate between Option>, T and () as return types. // Rust traits don't provide specialization and thus would encounter overlapping blanket impls, so we cannot use the type system here. @@ -1538,18 +1278,18 @@ fn make_function_definition(sig: &FnSignature, code: &FnCode) -> FnDefinition { } fn make_function_definition_with_defaults( - sig: &FnSignature, + sig: &dyn Function, code: &FnCode, full_fn_name: &Ident, ) -> (TokenStream, TokenStream) { let (default_fn_params, required_fn_params): (Vec<_>, Vec<_>) = sig - .params + .params() .iter() .partition(|arg| arg.default_value.is_some()); - let simple_fn_name = safe_ident(sig.function_name); + let simple_fn_name = safe_ident(sig.name()); let extended_fn_name = format_ident!("{}_ex", simple_fn_name); - let vis = make_vis(sig.is_private); + let vis = make_vis(sig.is_private()); let (builder_doc, surround_class_prefix) = make_extender_doc(sig, &extended_fn_name); @@ -1572,7 +1312,7 @@ fn make_function_definition_with_defaults( let receiver_param = &code.receiver.param; let receiver_self = &code.receiver.self_prefix; let (required_params, required_args) = make_params_and_args(&required_fn_params); - let return_decl = &sig.return_value.decl; + let return_decl = &sig.return_value().decl; // Technically, the builder would not need a lifetime -- it could just maintain an `object_ptr` copy. // However, this increases the risk that it is used out of place (not immediately for a default-param call). @@ -1638,13 +1378,13 @@ fn make_function_definition_with_defaults( (functions, builders) } -fn make_extender_doc(sig: &FnSignature, extended_fn_name: &Ident) -> (String, TokenStream) { +fn make_extender_doc(sig: &dyn Function, extended_fn_name: &Ident) -> (String, TokenStream) { // Not in the above match, because this is true for both static/instance methods. // Static/instance is determined by first argument (always use fully qualified function call syntax). let surround_class_prefix; let builder_doc; - match sig.surrounding_class { + match sig.surrounding_class() { Some(TyName { rust_ty, .. }) => { surround_class_prefix = quote! { re_export::#rust_ty:: }; builder_doc = format!( @@ -1667,17 +1407,19 @@ fn make_extender_doc(sig: &FnSignature, extended_fn_name: &Ident) -> (String, To (builder_doc, surround_class_prefix) } -fn make_extender_receiver(sig: &FnSignature) -> ExtenderReceiver { - let builder_mut = if matches!(sig.qualifier, FnQualifier::Const) { - quote! {} - } else { - quote! { mut } +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}") + } }; // Treat the object parameter like other parameters, as first in list. // Only add it if the method is not global or static. - match &sig.surrounding_class { - Some(surrounding_class) if !sig.qualifier.is_static_or_global() => { + match sig.surrounding_class() { + Some(surrounding_class) if !sig.qualifier().is_static_or_global() => { let class = &surrounding_class.rust_ty; ExtenderReceiver { @@ -1719,24 +1461,24 @@ struct Extender { } fn make_extender( - sig: &FnSignature, + sig: &dyn Function, object_fn_param: Option, default_fn_params: Vec<&FnParam>, ) -> Extender { // Note: could build a documentation string with default values here, but the Rust tokens are not very readable, // and often not helpful, such as Enum::from_ord(13). Maybe one day those could be resolved and curated. - let (lifetime, anon_lifetime) = if sig.qualifier.is_static_or_global() { + let (lifetime, anon_lifetime) = if sig.qualifier().is_static_or_global() { (TokenStream::new(), TokenStream::new()) } else { (quote! { <'a> }, quote! { <'_> }) }; - let all_fn_params = object_fn_param.iter().chain(&sig.params); + let all_fn_params = object_fn_param.iter().chain(sig.params().iter()); let len = all_fn_params.size_hint().0; let mut result = Extender { - builder_ty: format_ident!("Ex{}", conv::to_pascal_case(sig.function_name)), + builder_ty: format_ident!("Ex{}", conv::to_pascal_case(sig.name())), builder_lifetime: lifetime, builder_anon_lifetime: anon_lifetime, builder_methods: Vec::with_capacity(default_fn_params.len()), @@ -1784,27 +1526,23 @@ fn make_extender( result } -fn make_receiver(is_static: bool, is_const: bool, ffi_arg: TokenStream) -> FnReceiver { - // could reuse FnQualifier as parameter +fn make_receiver(qualifier: FnQualifier, ffi_arg_in: TokenStream) -> FnReceiver { + assert_ne!(qualifier, FnQualifier::Global, "expected class"); - let param = if is_static { - quote! {} - } else if is_const { - quote! { &self, } - } else { - quote! { &mut self, } + let param = match qualifier { + FnQualifier::Const => quote! { &self, }, + FnQualifier::Mut => quote! { &mut self, }, + FnQualifier::Static => quote! {}, + FnQualifier::Global => quote! {}, }; - let ffi_arg = if is_static { - quote! { std::ptr::null_mut() } + let (ffi_arg, self_prefix); + if matches!(qualifier, FnQualifier::Static) { + ffi_arg = quote! { std::ptr::null_mut() }; + self_prefix = quote! { Self:: }; } else { - ffi_arg - }; - - let self_prefix = if is_static { - quote! { Self:: } - } else { - quote! { self. } + ffi_arg = ffi_arg_in; + self_prefix = quote! { self. }; }; FnReceiver { @@ -1845,7 +1583,6 @@ fn make_params_and_args(method_args: &[&FnParam]) -> (Vec, Vec TokenStream { } } -fn make_virtual_method(method: &ClassMethod, ctx: &mut Context) -> TokenStream { - let method_name = virtual_method_name(method); +fn make_virtual_method(method: &ClassMethod) -> Option { + if !method.is_virtual() { + return None; + } // Virtual methods are never static. - assert!(!method.is_static); + let qualifier = method.qualifier(); + assert!(matches!(qualifier, FnQualifier::Mut | FnQualifier::Const)); let definition = make_function_definition( - &FnSignature { - function_name: method_name, - surrounding_class: None, // no default parameters needed for virtual methods - is_private: false, - is_virtual: true, - is_vararg: false, - qualifier: FnQualifier::for_method(method.is_const, method.is_static), - params: FnParam::new_range(&method.arguments, ctx), - return_value: FnReturn::new(&method.return_value, ctx), - }, + method, &FnCode { - receiver: make_receiver(false, method.is_const, TokenStream::new()), + receiver: make_receiver(qualifier, TokenStream::new()), // make_return() requests following args, but they are not used for virtual methods. We can provide empty streams. varcall_invocation: TokenStream::new(), ptrcall_invocation: TokenStream::new(), @@ -1938,7 +1669,7 @@ fn make_virtual_method(method: &ClassMethod, ctx: &mut Context) -> TokenStream { ); // Virtual methods have no builders. - definition.into_functions_only() + Some(definition.into_functions_only()) } fn make_all_virtual_methods( @@ -1946,78 +1677,53 @@ fn make_all_virtual_methods( all_base_names: &[TyName], ctx: &mut Context, ) -> Vec { - let mut all_virtuals = vec![]; - let mut extend_virtuals = |class| { - all_virtuals.extend( - get_methods_in_class(class) - .iter() - .filter(|m| m.is_virtual) - .cloned(), - ); - }; + let mut all_tokens = vec![]; - // Get virtuals defined on the current class. - extend_virtuals(class); - - // Add virtuals from superclasses. - for base in all_base_names { - let superclass = ctx.get_engine_class(base); - extend_virtuals(superclass); + for method in class.methods.iter() { + // Assumes that inner function filters on is_virtual. + if let Some(tokens) = make_virtual_method(method) { + all_tokens.push(tokens); + } } - all_virtuals - .into_iter() - .filter_map(|method| { - if codegen_special_cases::is_class_method_excluded(&method, true, ctx) { - None - } else { - Some(make_virtual_method(&method, ctx)) + for base_name in all_base_names { + let json_base_class = ctx.get_engine_class(base_name); + for json_method in option_as_slice(&json_base_class.methods) { + if !json_method.is_virtual { + continue; } - }) - .collect() -} -fn get_methods_in_class(class: &Class) -> &[ClassMethod] { - match &class.methods { - None => &[], - Some(methods) => methods, - } -} + // FIXME temporary workaround, the ctx doesn't cross-over borrowed fields in ctx + let hack_ptr = ctx as *const _ as *mut _; + let hack_ctx = unsafe { &mut *hack_ptr }; // UB -fn virtual_method_name(class_method: &ClassMethod) -> &str { - // Matching the C++ convention, we remove the leading underscore - // from virtual method names. - let method_name = class_method - .name - .strip_prefix('_') - .unwrap_or(&class_method.name); - - // As a special exception, a few classes define a virtual method - // called "_init" (distinct from the constructor), so we rename - // those to avoid a name conflict in our trait. - if method_name == "init" { - "init_ext" - } else { - method_name + if let Some(method) = ClassMethod::from_json(json_method, class.name(), hack_ctx) { + if let Some(tokens) = make_virtual_method(&method) { + all_tokens.push(tokens); + } + } + } } + + all_tokens } -fn function_uses_pointers(sig: &FnSignature) -> bool { +fn function_uses_pointers(sig: &dyn Function) -> bool { let has_pointer_params = sig - .params + .params() .iter() .any(|param| matches!(param.type_, RustTy::RawPointer { .. })); - let has_pointer_return = matches!(sig.return_value.type_, Some(RustTy::RawPointer { .. })); + let has_pointer_return = matches!(sig.return_value().type_, Some(RustTy::RawPointer { .. })); // No short-circuiting due to variable decls, but that's fine. has_pointer_params || has_pointer_return } -fn function_uses_default_params(sig: &FnSignature) -> bool { - sig.params.iter().any(|arg| arg.default_value.is_some()) +fn function_uses_default_params(sig: &dyn Function) -> bool { + sig.params().iter().any(|arg| arg.default_value.is_some()) && !special_cases::is_method_excluded_from_default_params( - sig.surrounding_class, - sig.function_name, + sig.surrounding_class(), + sig.name(), ) } diff --git a/godot-codegen/src/codegen_special_cases.rs b/godot-codegen/src/codegen_special_cases.rs index 2ee5d22a2..0678c5a44 100644 --- a/godot-codegen/src/codegen_special_cases.rs +++ b/godot-codegen/src/codegen_special_cases.rs @@ -7,29 +7,33 @@ //! Codegen-dependent exclusions. Can be removed if feature `codegen-full` is removed. -use crate::api_parser::{BuiltinClassMethod, ClassMethod, UtilityFunction}; +// TODO make this file private and only accessed by special_cases.rs. + use crate::context::Context; -use crate::{special_cases, TyName}; +use crate::models::domain::TyName; +use crate::models::json::{JsonBuiltinMethod, JsonClassMethod, JsonUtilityFunction}; +use crate::special_cases; -pub(crate) fn is_builtin_method_excluded(method: &BuiltinClassMethod) -> bool { +pub(crate) fn is_builtin_method_excluded(method: &JsonBuiltinMethod) -> bool { // TODO Fall back to varcall (recent addition in GDExtension API). // See https://github.com/godot-rust/gdext/issues/382. method.is_vararg } #[cfg(not(feature = "codegen-full"))] -pub(crate) fn is_class_excluded(class: &str) -> bool { - !SELECTED_CLASSES.contains(&class) +pub(crate) fn is_class_excluded(godot_class_name: &str) -> bool { + !SELECTED_CLASSES.contains(&godot_class_name) } #[cfg(feature = "codegen-full")] -pub(crate) fn is_class_excluded(_class: &str) -> bool { +pub(crate) fn is_class_excluded(_godot_class_name: &str) -> bool { false } #[cfg(not(feature = "codegen-full"))] fn is_type_excluded(ty: &str, ctx: &mut Context) -> bool { - use crate::{conv, RustTy}; + use crate::conv; + use crate::models::domain::RustTy; fn is_rust_type_excluded(ty: &RustTy) -> bool { match ty { @@ -55,22 +59,19 @@ fn is_type_excluded(ty: &str, ctx: &mut Context) -> bool { is_rust_type_excluded(&conv::to_rust_type(ty, None, ctx)) } -pub(crate) fn is_class_method_excluded( - method: &ClassMethod, - is_virtual_impl: bool, - ctx: &mut Context, -) -> bool { +#[cfg(feature = "codegen-full")] +fn is_type_excluded(_ty: &str, _ctx: &mut Context) -> bool { + false +} + +pub(crate) fn is_class_method_excluded(method: &JsonClassMethod, ctx: &mut Context) -> bool { let is_arg_or_return_excluded = |ty: &str, _ctx: &mut Context| { - let class_deleted = special_cases::is_class_deleted(&TyName::from_godot(ty)); + // First check if the type is explicitly deleted. In Godot, type names are unique without further categorization, + // so passing in a class name while checking for any types is fine. + let class_deleted = special_cases::is_godot_type_deleted(&TyName::from_godot(ty)); - #[cfg(not(feature = "codegen-full"))] - { - class_deleted || is_type_excluded(ty, _ctx) - } - #[cfg(feature = "codegen-full")] - { - class_deleted - } + // Then also check if the type is excluded from codegen (due to current Cargo feature. RHS is always false in full-codegen. + class_deleted || is_type_excluded(ty, _ctx) }; // Exclude if return type contains an excluded type. @@ -88,21 +89,22 @@ pub(crate) fn is_class_method_excluded( return true; } - // Virtual methods are not part of the class API itself, but exposed as an accompanying trait. - if !is_virtual_impl && method.name.starts_with('_') { - return true; - } - false } #[cfg(feature = "codegen-full")] -pub(crate) fn is_function_excluded(_function: &UtilityFunction, _ctx: &mut Context) -> bool { +pub(crate) fn is_utility_function_excluded( + _function: &JsonUtilityFunction, + _ctx: &mut Context, +) -> bool { false } #[cfg(not(feature = "codegen-full"))] -pub(crate) fn is_function_excluded(function: &UtilityFunction, ctx: &mut Context) -> bool { +pub(crate) fn is_utility_function_excluded( + function: &JsonUtilityFunction, + ctx: &mut Context, +) -> bool { function .return_type .as_ref() diff --git a/godot-codegen/src/context.rs b/godot-codegen/src/context.rs index 1394a86f0..7791d94bd 100644 --- a/godot-codegen/src/context.rs +++ b/godot-codegen/src/context.rs @@ -5,16 +5,19 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::api_parser::{BuiltinClass, BuiltinClassMethod, Class, ClassConstant, ClassMethod}; +use crate::models::domain::{GodotTy, RustTy, TyName}; +use crate::models::json::{ + JsonBuiltinClass, JsonBuiltinMethod, JsonClass, JsonClassConstant, JsonClassMethod, +}; use crate::util::{option_as_slice, MethodTableKey}; -use crate::{codegen_special_cases, special_cases, util, ExtensionApi, GodotTy, RustTy, TyName}; +use crate::{codegen_special_cases, special_cases, util, JsonExtensionApi}; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, ToTokens}; use std::collections::{HashMap, HashSet}; #[derive(Default)] -pub(crate) struct Context<'a> { - engine_classes: HashMap, +pub struct Context<'a> { + engine_classes: HashMap, builtin_types: HashSet<&'a str>, native_structures_types: HashSet<&'a str>, singletons: HashSet<&'a str>, @@ -27,7 +30,7 @@ pub(crate) struct Context<'a> { } impl<'a> Context<'a> { - pub fn build_from_api(api: &'a ExtensionApi) -> Self { + pub fn build_from_api(api: &'a JsonExtensionApi) -> Self { let mut ctx = Self::default(); for class in api.singletons.iter() { @@ -127,7 +130,7 @@ impl<'a> Context<'a> { fn populate_notification_constants( class_name: &TyName, - constants: &[ClassConstant], + constants: &[JsonClassConstant], ctx: &mut Context, ) { let mut has_notifications = false; @@ -155,9 +158,9 @@ impl<'a> Context<'a> { } fn populate_class_table_indices( - class: &Class, + class: &JsonClass, class_name: &TyName, - methods: &[ClassMethod], + methods: &[JsonClassMethod], ctx: &mut Context, ) { if special_cases::is_class_deleted(class_name) { @@ -165,7 +168,8 @@ impl<'a> Context<'a> { } for method in methods.iter() { - if special_cases::is_class_method_deleted(class_name, method, ctx) { + if special_cases::is_class_method_deleted(class_name, method, ctx) || method.is_virtual + { continue; } @@ -180,8 +184,8 @@ impl<'a> Context<'a> { } fn populate_builtin_class_table_indices( - builtin: &BuiltinClass, - methods: &[BuiltinClassMethod], + builtin: &JsonBuiltinClass, + methods: &[JsonBuiltinMethod], ctx: &mut Context, ) { let builtin_ty = TyName::from_godot(builtin.name.as_str()); @@ -203,7 +207,7 @@ impl<'a> Context<'a> { } } - pub fn get_engine_class(&self, class_name: &TyName) -> &Class { + pub fn get_engine_class(&self, class_name: &TyName) -> &JsonClass { self.engine_classes.get(class_name).unwrap() } @@ -327,7 +331,7 @@ impl ToTokens for NotificationEnum { /// Maintains class hierarchy. Uses Rust class names, not Godot ones. #[derive(Default)] -pub(crate) struct InheritanceTree { +pub struct InheritanceTree { derived_to_base: HashMap, } diff --git a/godot-codegen/src/conv/name_conversions.rs b/godot-codegen/src/conv/name_conversions.rs index 0ba9f79ef..b4c7a212d 100644 --- a/godot-codegen/src/conv/name_conversions.rs +++ b/godot-codegen/src/conv/name_conversions.rs @@ -44,6 +44,7 @@ pub fn to_snake_case(class_name: &str) -> String { .replace("GDExtension", "Gdextension") .replace("VSync", "Vsync") .replace("SDFGIY", "SdfgiY") + .replace("ENet", "Enet") .to_snake_case() } @@ -220,6 +221,7 @@ fn reduce_hardcoded_prefix( // Inconsistency with varying prefixes. (Some("RenderingServer" | "Mesh"), "ArrayFormat") => &["ARRAY_FORMAT_", "ARRAY_"], (Some("AudioServer"), "SpeakerMode") => &["SPEAKER_MODE_", "SPEAKER_"], + (Some("ENetConnection"), "HostStatistic") => &["HOST_"], // do not remove TOTAL_ prefix (shared by all), e.g. TOTAL_SENT_DATA. (None, "MethodFlags") => &["METHOD_FLAG_", "METHOD_FLAGS_"], // There are two "mask" entries which span multiple bits: KEY_CODE_MASK, KEY_MODIFIER_MASK -> CODE_MASK, MODIFIER_MASK. diff --git a/godot-codegen/src/conv/type_conversions.rs b/godot-codegen/src/conv/type_conversions.rs index 2696e1d2f..bf2c27173 100644 --- a/godot-codegen/src/conv/type_conversions.rs +++ b/godot-codegen/src/conv/type_conversions.rs @@ -12,9 +12,10 @@ use quote::{quote, ToTokens}; use std::fmt; use crate::context::Context; +use crate::models::domain::{GodotTy, RustTy, TyName}; use crate::special_cases::is_builtin_type_scalar; use crate::util::{ident, unmap_meta}; -use crate::{conv, GodotTy, ModName, RustTy, TyName}; +use crate::{conv, ModName}; // ---------------------------------------------------------------------------------------------------------------------------------------------- // Godot -> Rust types diff --git a/godot-codegen/src/lib.rs b/godot-codegen/src/lib.rs index a09267614..99c88c314 100644 --- a/godot-codegen/src/lib.rs +++ b/godot-codegen/src/lib.rs @@ -5,13 +5,13 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -mod api_parser; mod central_generator; mod class_generator; mod codegen_special_cases; mod context; mod conv; mod interface_generator; +mod models; mod special_cases; mod util; mod utilities_generator; @@ -19,26 +19,22 @@ mod utilities_generator; #[cfg(test)] mod tests; -use api_parser::{load_extension_api, ExtensionApi}; -use central_generator::{ - generate_core_central_file, generate_core_mod_file, generate_sys_central_file, - generate_sys_classes_file, +use crate::central_generator::{ + generate_core_central_file, generate_core_mod_file, generate_sys_builtin_lifecycle_file, + generate_sys_builtin_methods_file, generate_sys_central_file, generate_sys_classes_file, + generate_sys_utilities_file, }; -use class_generator::{ +use crate::class_generator::{ generate_builtin_class_files, generate_class_files, generate_native_structures_files, }; -use context::Context; -use interface_generator::generate_sys_interface_file; -use util::ident; -use utilities_generator::generate_utilities_file; - -use crate::central_generator::{ - generate_sys_builtin_lifecycle_file, generate_sys_builtin_methods_file, - generate_sys_utilities_file, BuiltinTypeMap, -}; -use crate::context::NotificationEnum; +use crate::context::{Context, NotificationEnum}; +use crate::interface_generator::generate_sys_interface_file; +use crate::models::domain::{ExtensionApi, TyName}; +use crate::models::json::{load_extension_api, JsonExtensionApi}; +use crate::util::ident; +use crate::utilities_generator::generate_utilities_file; use proc_macro2::{Ident, TokenStream}; -use quote::{quote, ToTokens}; +use quote::ToTokens; use std::path::{Path, PathBuf}; pub type SubmitFn = dyn FnMut(PathBuf, TokenStream); @@ -66,27 +62,34 @@ pub fn generate_sys_files( h_path: &Path, watch: &mut godot_bindings::StopWatch, ) { - let (api, build_config) = load_extension_api(watch); - let mut ctx = Context::build_from_api(&api); + let json_api = load_extension_api(watch); + + let mut ctx = Context::build_from_api(&json_api); watch.record("build_context"); - generate_sys_central_file(&api, &mut ctx, build_config, sys_gen_path, &mut submit_fn); + let api = ExtensionApi::from_json(&json_api, &mut ctx); + watch.record("map_domain_models"); + + // TODO if ctx is no longer needed for below functions: + // Deallocate all the JSON models; no longer needed for codegen. + // drop(json_api); + + generate_sys_central_file(&api, &mut ctx, sys_gen_path, &mut submit_fn); watch.record("generate_central_file"); - let builtin_types = BuiltinTypeMap::load(&api); - generate_sys_builtin_methods_file(&api, &builtin_types, sys_gen_path, &mut ctx, &mut submit_fn); + generate_sys_builtin_methods_file(&api, sys_gen_path, &mut ctx, &mut submit_fn); watch.record("generate_builtin_methods_file"); - generate_sys_builtin_lifecycle_file(&builtin_types, sys_gen_path, &mut submit_fn); + generate_sys_builtin_lifecycle_file(&api, sys_gen_path, &mut submit_fn); watch.record("generate_builtin_lifecycle_file"); generate_sys_classes_file(&api, sys_gen_path, watch, &mut ctx, &mut submit_fn); // watch records inside the function. - generate_sys_utilities_file(&api, sys_gen_path, &mut ctx, &mut submit_fn); + generate_sys_utilities_file(&api, sys_gen_path, &mut submit_fn); watch.record("generate_utilities_file"); - let is_godot_4_0 = api.header.version_major == 4 && api.header.version_minor == 0; + let is_godot_4_0 = api.godot_version.major == 4 && api.godot_version.minor == 0; generate_sys_interface_file(h_path, sys_gen_path, is_godot_4_0, &mut submit_fn); watch.record("generate_interface_file"); } @@ -96,14 +99,21 @@ pub fn generate_core_files(core_gen_path: &Path) { generate_core_mod_file(core_gen_path, &mut submit_fn); - let (api, build_config) = load_extension_api(&mut watch); - let mut ctx = Context::build_from_api(&api); + let json_api = load_extension_api(&mut watch); + let mut ctx = Context::build_from_api(&json_api); watch.record("build_context"); - generate_core_central_file(&api, &mut ctx, build_config, core_gen_path, &mut submit_fn); + let api = ExtensionApi::from_json(&json_api, &mut ctx); + watch.record("map_domain_models"); + + // TODO if ctx is no longer needed for below functions: + // Deallocate all the JSON models; no longer needed for codegen. + // drop(json_api); + + generate_core_central_file(&api, &mut ctx, core_gen_path, &mut submit_fn); watch.record("generate_central_file"); - generate_utilities_file(&api, &mut ctx, core_gen_path, &mut submit_fn); + generate_utilities_file(&api, core_gen_path, &mut submit_fn); watch.record("generate_utilities_file"); // Class files -- currently output in godot-core; could maybe be separated cleaner @@ -111,7 +121,6 @@ pub fn generate_core_files(core_gen_path: &Path) { generate_class_files( &api, &mut ctx, - build_config, &core_gen_path.join("classes"), &mut submit_fn, ); @@ -120,7 +129,6 @@ pub fn generate_core_files(core_gen_path: &Path) { generate_builtin_class_files( &api, &mut ctx, - build_config, &core_gen_path.join("builtin_classes"), &mut submit_fn, ); @@ -129,7 +137,6 @@ pub fn generate_core_files(core_gen_path: &Path) { generate_native_structures_files( &api, &mut ctx, - build_config, &core_gen_path.join("native"), &mut submit_fn, ); @@ -138,143 +145,11 @@ pub fn generate_core_files(core_gen_path: &Path) { watch.write_stats_to(&core_gen_path.join("codegen-stats.txt")); } -// ---------------------------------------------------------------------------------------------------------------------------------------------- -// Shared utility types - -// Same as above, without lifetimes - -#[derive(Clone, Eq, PartialEq, Hash, Debug)] -struct GodotTy { - ty: String, - meta: Option, -} - -// impl GodotTy { -// fn new<'a>(ty: &'a String, meta: &'a Option) -> Self { -// Self { -// ty: ty.clone(), -// meta: meta.clone(), -// } -// } -// } - -// ---------------------------------------------------------------------------------------------------------------------------------------------- - -#[derive(Clone, Debug)] -enum RustTy { - /// `bool`, `Vector3i` - BuiltinIdent(Ident), - - /// `Array` - BuiltinArray(TokenStream), - - /// C-style raw pointer to a `RustTy`. - RawPointer { inner: Box, is_const: bool }, - - /// `Array>` - EngineArray { - tokens: TokenStream, - #[allow(dead_code)] // only read in minimal config - elem_class: String, - }, - - /// `module::Enum` - EngineEnum { - tokens: TokenStream, - /// `None` for globals - #[allow(dead_code)] // only read in minimal config - surrounding_class: Option, - }, - - /// `module::Bitfield` - EngineBitfield { - tokens: TokenStream, - /// `None` for globals - #[allow(dead_code)] // only read in minimal config - surrounding_class: Option, - }, - - /// `Gd` - EngineClass { - /// Tokens with full `Gd` - tokens: TokenStream, - /// only inner `T` - #[allow(dead_code)] // only read in minimal config - inner_class: Ident, - }, -} - -impl RustTy { - pub fn return_decl(&self) -> TokenStream { - match self { - Self::EngineClass { tokens, .. } => quote! { -> Option<#tokens> }, - other => quote! { -> #other }, - } - } -} - -impl ToTokens for RustTy { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - RustTy::BuiltinIdent(ident) => ident.to_tokens(tokens), - RustTy::BuiltinArray(path) => path.to_tokens(tokens), - RustTy::RawPointer { - inner, - is_const: true, - } => quote! { *const #inner }.to_tokens(tokens), - RustTy::RawPointer { - inner, - is_const: false, - } => quote! { *mut #inner }.to_tokens(tokens), - RustTy::EngineArray { tokens: path, .. } => path.to_tokens(tokens), - RustTy::EngineEnum { tokens: path, .. } => path.to_tokens(tokens), - RustTy::EngineBitfield { tokens: path, .. } => path.to_tokens(tokens), - RustTy::EngineClass { tokens: path, .. } => path.to_tokens(tokens), - } - } -} - -// ---------------------------------------------------------------------------------------------------------------------------------------------- - -/// Contains multiple naming conventions for types (classes, builtin classes, enums). -// TODO(bromeon, 2023-09): see if it makes sense to unify this with TypeNames (which is mostly used in central generator) -#[derive(Clone, Eq, PartialEq, Hash)] -pub(crate) struct TyName { - godot_ty: String, - rust_ty: Ident, -} - -impl TyName { - fn from_godot(godot_ty: &str) -> Self { - Self { - godot_ty: godot_ty.to_owned(), - rust_ty: ident(&conv::to_pascal_case(godot_ty)), - } - } - - fn description(&self) -> String { - if self.rust_ty == self.godot_ty { - self.godot_ty.clone() - } else { - format!("{} [renamed {}]", self.godot_ty, self.rust_ty) - } - } - - fn virtual_trait_name(&self) -> String { - format!("I{}", self.rust_ty) - } -} - -impl ToTokens for TyName { - fn to_tokens(&self, tokens: &mut TokenStream) { - self.rust_ty.to_tokens(tokens) - } -} - // ---------------------------------------------------------------------------------------------------------------------------------------------- /// Contains naming conventions for modules. -pub(crate) struct ModName { +#[derive(Clone)] +pub struct ModName { // godot_mod: String, rust_mod: Ident, } @@ -315,6 +190,6 @@ struct GeneratedClassModule { } struct GeneratedBuiltinModule { - class_name: TyName, + symbol_ident: Ident, module_name: ModName, } diff --git a/godot-codegen/src/models/domain.rs b/godot-codegen/src/models/domain.rs new file mode 100644 index 000000000..48ec18e41 --- /dev/null +++ b/godot-codegen/src/models/domain.rs @@ -0,0 +1,718 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +#![allow(dead_code)] // TODO remove when mapped + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Domain models + +use crate::context::Context; +use crate::models::json::{JsonMethodArg, JsonMethodReturn}; +use crate::util::{ident, option_as_slice, safe_ident, ClassCodegenLevel}; +use crate::{conv, ModName}; + +use proc_macro2::{Ident, Literal, TokenStream}; +use quote::{format_ident, quote, ToTokens}; +use std::fmt; + +pub struct ExtensionApi { + pub builtins: Vec, + pub classes: Vec, + pub singletons: Vec, + pub native_structures: Vec, + pub utility_functions: Vec, + pub global_enums: Vec, + pub godot_version: GodotApiVersion, + + /// Map `(original Godot name, build config) -> builtin size` in bytes. + pub builtin_sizes: Vec, +} + +impl ExtensionApi { + /// O(n) search time, often leads to O(n^2), but less than 40 builtins total. + pub fn builtin_by_original_name(&self, name: &str) -> &BuiltinVariant { + self.builtins + .iter() + .find(|b| b.godot_original_name() == name) + .unwrap_or_else(|| panic!("builtin_by_name: invalid `{}`", name)) + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Builtins + classes + singletons + +pub trait ClassLike { + fn common(&self) -> &ClassCommons; + + fn name(&self) -> &TyName { + &self.common().name + } + + fn mod_name(&self) -> &ModName { + &self.common().mod_name + } +} + +pub struct ClassCommons { + pub name: TyName, + pub mod_name: ModName, +} + +pub struct BuiltinClass { + pub common: ClassCommons, + pub inner_name: TyName, + pub methods: Vec, + pub constructors: Vec, + pub operators: Vec, + pub has_destructor: bool, + pub enums: Vec, +} + +impl BuiltinClass { + pub fn inner_name(&self) -> &Ident { + &self.inner_name.rust_ty + } +} + +impl ClassLike for BuiltinClass { + fn common(&self) -> &ClassCommons { + &self.common + } +} + +/// All information about a builtin type, including its type (if available). +pub struct BuiltinVariant { + pub godot_original_name: String, + pub godot_shout_name: String, + pub godot_snake_name: String, + pub builtin_class: Option, + + pub variant_type_ord: i32, +} + +impl BuiltinVariant { + /// Name in JSON for the class: `"int"` or `"PackedVector2Array"`. + pub fn godot_original_name(&self) -> &str { + &self.godot_original_name + } + + /// Name in JSON: `"INT"` or `"PACKED_VECTOR2_ARRAY"`. + pub fn godot_shout_name(&self) -> &str { + &self.godot_shout_name + } + + /// snake_case name like `"packed_vector2_array"`. + pub fn snake_name(&self) -> &str { + &self.godot_snake_name + } + + /// Excludes variant types if: + /// - There is no builtin class definition in the JSON. For example, `OBJECT` is a variant type but has no corresponding class. + /// - The type is so trivial that most of its operations are directly provided by Rust, and there is no need + /// to expose the construct/destruct/operator methods from Godot (e.g. `int`, `bool`). + /// + /// See also `BuiltinClass::from_json()` + pub fn associated_builtin_class(&self) -> Option<&BuiltinClass> { + self.builtin_class.as_ref() + } + + /// Returns an ident like `GDEXTENSION_VARIANT_TYPE_PACKED_VECTOR2_ARRAY`. + pub fn sys_variant_type(&self) -> Ident { + format_ident!("GDEXTENSION_VARIANT_TYPE_{}", self.godot_shout_name()) + } + + pub fn unsuffixed_ord_lit(&self) -> Literal { + Literal::i32_unsuffixed(self.variant_type_ord) + } +} + +pub struct Class { + pub common: ClassCommons, + pub is_refcounted: bool, + pub is_instantiable: bool, + pub inherits: Option, + pub api_level: ClassCodegenLevel, + pub constants: Vec, + pub enums: Vec, + pub methods: Vec, +} + +impl ClassLike for Class { + fn common(&self) -> &ClassCommons { + &self.common + } +} + +pub struct NativeStructure { + pub name: String, + pub format: String, +} + +pub struct Singleton { + pub name: TyName, + // Note: `type` currently has always same value as `name`, thus redundant + // type_: String, +} + +pub struct Enum { + pub name: Ident, + pub godot_name: String, + pub is_bitfield: bool, + pub enumerators: Vec, +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Enumerators + +pub struct Enumerator { + pub name: Ident, + + pub godot_name: String, + + // i64 is common denominator for enum, bitfield and constant values. + // Note that values > i64::MAX will be implicitly wrapped, see https://github.com/not-fl3/nanoserde/issues/89. + pub value: EnumeratorValue, +} +pub enum EnumeratorValue { + Enum(i32), + Bitfield(u64), +} + +impl EnumeratorValue { + pub fn to_i64(&self) -> i64 { + // Conversion is safe because i64 is used in the original JSON. + match self { + EnumeratorValue::Enum(i) => *i as i64, + EnumeratorValue::Bitfield(i) => *i as i64, + } + } + + /// This method is needed for platform-dependent types like raw `VariantOperator`, which can be `i32` or `u32`. + /// Do not suffix them. + /// + /// See also `BuiltinVariant::unsuffixed_ord_lit()`. + pub fn unsuffixed_lit(&self) -> Literal { + Literal::i64_unsuffixed(self.to_i64()) + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Constants + +trait Constant { + fn name(&self) -> &str; +} + +pub struct ClassConstant { + pub name: String, + pub value: ClassConstantValue, +} + +pub enum ClassConstantValue { + I32(i32), + I64(i64), +} + +pub struct Operator { + pub symbol: String, + //pub right_type: Option, // null if unary + //pub return_type: String, +} + +pub struct Constructor { + pub index: usize, + pub raw_parameters: Vec, + // pub parameters: Vec, +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Build config + version + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub enum BuildConfiguration { + Float32, + Float64, + Double32, + Double64, +} + +impl BuildConfiguration { + #[cfg(feature = "double-precision")] + pub fn is_applicable(self) -> bool { + matches!(self, Self::Double32 | Self::Double64) + } + + #[cfg(not(feature = "double-precision"))] + pub fn is_applicable(self) -> bool { + matches!(self, Self::Float32 | Self::Float64) + } + + // Rewrite the above using #[cfg]. + #[cfg(feature = "double-precision")] + pub fn all_applicable() -> [BuildConfiguration; 2] { + [BuildConfiguration::Double32, BuildConfiguration::Double64] + } + + #[cfg(not(feature = "double-precision"))] + pub fn all_applicable() -> [BuildConfiguration; 2] { + [BuildConfiguration::Float32, BuildConfiguration::Float64] + } + + pub fn is_64bit(self) -> bool { + matches!(self, Self::Float64 | Self::Double64) + } +} + +pub struct BuiltinSize { + pub builtin_original_name: String, + pub config: BuildConfiguration, + pub size: usize, +} + +/// Godot API version (from the JSON; not runtime version). +// Could be consolidated with versions in other part of codegen, e.g. the one in godot-bindings. +#[derive(Clone)] +pub struct GodotApiVersion { + pub major: u8, + pub minor: u8, + pub patch: u8, + + /// Without "Godot Engine " prefix. + pub version_string: String, +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +/// Stuff that is in every of the "function" types. +pub struct FunctionCommon { + pub name: String, + pub godot_name: String, + pub parameters: Vec, + pub return_value: FnReturn, + pub is_vararg: bool, + pub is_private: bool, + pub direction: FnDirection, +} + +pub trait Function: fmt::Display { + // Required: + fn common(&self) -> &FunctionCommon; + fn qualifier(&self) -> FnQualifier; + fn surrounding_class(&self) -> Option<&TyName>; + + // Default: + fn name(&self) -> &str { + &self.common().name + } + fn godot_name(&self) -> &str { + &self.common().godot_name + } + fn params(&self) -> &[FnParam] { + &self.common().parameters + } + fn return_value(&self) -> &FnReturn { + &self.common().return_value + } + fn is_vararg(&self) -> bool { + self.common().is_vararg + } + fn is_private(&self) -> bool { + self.common().is_private + } + fn is_virtual(&self) -> bool { + matches!(self.direction(), FnDirection::Virtual) + } + fn direction(&self) -> FnDirection { + self.common().direction + } +} + +#[deprecated] +struct FnSignature<'a> { + function_name: &'a str, + surrounding_class: Option<&'a TyName>, // None if global function + is_private: bool, + is_virtual: bool, + is_vararg: bool, + qualifier: FnQualifier, + params: Vec, + return_value: FnReturn, +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +pub struct UtilityFunction { + pub common: FunctionCommon, +} + +impl UtilityFunction { + pub fn hash(&self) -> i64 { + match self.direction() { + FnDirection::Virtual => unreachable!("utility function cannot be virtual"), + FnDirection::Outbound { hash } => hash, + } + } +} + +impl Function for UtilityFunction { + fn common(&self) -> &FunctionCommon { + &self.common + } + + fn qualifier(&self) -> FnQualifier { + FnQualifier::Global + } + + fn surrounding_class(&self) -> Option<&TyName> { + None + } +} + +impl fmt::Display for UtilityFunction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "utility function `{}`", self.name()) + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +pub struct BuiltinMethod { + // variant_type: + pub common: FunctionCommon, + pub qualifier: FnQualifier, + pub surrounding_class: TyName, +} + +impl BuiltinMethod { + pub fn hash(&self) -> i64 { + match self.direction() { + FnDirection::Virtual => unreachable!("builtin method cannot be virtual"), + FnDirection::Outbound { hash } => hash, + } + } +} + +impl Function for BuiltinMethod { + fn common(&self) -> &FunctionCommon { + &self.common + } + + fn qualifier(&self) -> FnQualifier { + self.qualifier + } + + fn surrounding_class(&self) -> Option<&TyName> { + Some(&self.surrounding_class) + } +} + +impl fmt::Display for BuiltinMethod { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "builtin method `{}::{}`", + self.surrounding_class.rust_ty, + self.name(), + ) + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +pub struct ClassMethod { + pub common: FunctionCommon, + pub qualifier: FnQualifier, + pub surrounding_class: TyName, +} + +impl ClassMethod {} + +impl Function for ClassMethod { + fn common(&self) -> &FunctionCommon { + &self.common + } + + fn qualifier(&self) -> FnQualifier { + self.qualifier + } + + fn surrounding_class(&self) -> Option<&TyName> { + Some(&self.surrounding_class) + } +} + +impl fmt::Display for ClassMethod { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "class method `{}::{}`", + self.surrounding_class.rust_ty, + self.name(), + ) + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +#[derive(Copy, Clone, Debug)] +pub enum FnDirection { + /// Godot -> Rust. + Virtual, + + /// Rust -> Godot. + Outbound { hash: i64 }, +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum FnQualifier { + Mut, // &mut self + Const, // &self + Static, // Self + Global, // (nothing) // TODO remove? +} + +impl FnQualifier { + pub fn from_const_static(is_const: bool, is_static: bool) -> FnQualifier { + if is_static { + assert!( + !is_const, + "const and static qualifiers are mutually exclusive" + ); + FnQualifier::Static + } else if is_const { + FnQualifier::Const + } else { + FnQualifier::Mut + } + } + + pub fn is_static_or_global(&self) -> bool { + matches!(self, Self::Static | Self::Global) + } +} +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +pub struct FnParam { + pub name: Ident, + pub type_: RustTy, + pub default_value: Option, +} + +impl FnParam { + pub fn new_range(method_args: &Option>, ctx: &mut Context) -> Vec { + option_as_slice(method_args) + .iter() + .map(|arg| Self::new(arg, ctx)) + .collect() + } + + pub fn new_range_no_defaults( + method_args: &Option>, + ctx: &mut Context, + ) -> Vec { + option_as_slice(method_args) + .iter() + .map(|arg| Self::new_no_defaults(arg, ctx)) + .collect() + } + + pub fn new(method_arg: &JsonMethodArg, ctx: &mut Context) -> FnParam { + let name = safe_ident(&method_arg.name); + let type_ = conv::to_rust_type(&method_arg.type_, method_arg.meta.as_ref(), ctx); + let default_value = method_arg + .default_value + .as_ref() + .map(|v| conv::to_rust_expr(v, &type_)); + + FnParam { + name, + type_, + default_value, + } + } + + pub fn new_no_defaults(method_arg: &JsonMethodArg, ctx: &mut Context) -> FnParam { + FnParam { + name: safe_ident(&method_arg.name), + type_: conv::to_rust_type(&method_arg.type_, method_arg.meta.as_ref(), ctx), + //type_: to_rust_type(&method_arg.type_, &method_arg.meta, ctx), + default_value: None, + } + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +pub struct FnReturn { + pub decl: TokenStream, + pub type_: Option, +} + +impl FnReturn { + pub fn new(return_value: &Option, ctx: &mut Context) -> Self { + if let Some(ret) = return_value { + let ty = conv::to_rust_type(&ret.type_, ret.meta.as_ref(), ctx); + + Self { + decl: ty.return_decl(), + type_: Some(ty), + } + } else { + Self { + decl: TokenStream::new(), + type_: None, + } + } + } + + pub fn type_tokens(&self) -> TokenStream { + match &self.type_ { + Some(RustTy::EngineClass { tokens, .. }) => { + quote! { Option<#tokens> } + } + Some(ty) => { + quote! { #ty } + } + _ => { + quote! { () } + } + } + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Godot type + +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub struct GodotTy { + pub ty: String, + pub meta: Option, +} + +// impl GodotTy { +// fn new<'a>(ty: &'a String, meta: &'a Option) -> Self { +// Self { +// ty: ty.clone(), +// meta: meta.clone(), +// } +// } +// } + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Rust type + +#[derive(Clone, Debug)] +pub enum RustTy { + /// `bool`, `Vector3i` + BuiltinIdent(Ident), + + /// `Array` + BuiltinArray(TokenStream), + + /// C-style raw pointer to a `RustTy`. + RawPointer { inner: Box, is_const: bool }, + + /// `Array>` + EngineArray { + tokens: TokenStream, + #[allow(dead_code)] // only read in minimal config + elem_class: String, + }, + + /// `module::Enum` + EngineEnum { + tokens: TokenStream, + /// `None` for globals + #[allow(dead_code)] // only read in minimal config + surrounding_class: Option, + }, + + /// `module::Bitfield` + EngineBitfield { + tokens: TokenStream, + /// `None` for globals + #[allow(dead_code)] // only read in minimal config + surrounding_class: Option, + }, + + /// `Gd` + EngineClass { + /// Tokens with full `Gd` + tokens: TokenStream, + /// only inner `T` + #[allow(dead_code)] // only read in minimal config + inner_class: Ident, + }, +} + +impl RustTy { + pub fn return_decl(&self) -> TokenStream { + match self { + Self::EngineClass { tokens, .. } => quote! { -> Option<#tokens> }, + other => quote! { -> #other }, + } + } +} + +impl ToTokens for RustTy { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + RustTy::BuiltinIdent(ident) => ident.to_tokens(tokens), + RustTy::BuiltinArray(path) => path.to_tokens(tokens), + RustTy::RawPointer { + inner, + is_const: true, + } => quote! { *const #inner }.to_tokens(tokens), + RustTy::RawPointer { + inner, + is_const: false, + } => quote! { *mut #inner }.to_tokens(tokens), + RustTy::EngineArray { tokens: path, .. } => path.to_tokens(tokens), + RustTy::EngineEnum { tokens: path, .. } => path.to_tokens(tokens), + RustTy::EngineBitfield { tokens: path, .. } => path.to_tokens(tokens), + RustTy::EngineClass { tokens: path, .. } => path.to_tokens(tokens), + } + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +/// Contains multiple naming conventions for types (classes, builtin classes, enums). +#[derive(Clone, Eq, PartialEq, Hash)] +pub struct TyName { + pub godot_ty: String, + pub rust_ty: Ident, +} + +impl TyName { + pub fn from_godot(godot_ty: &str) -> Self { + Self { + godot_ty: godot_ty.to_owned(), + rust_ty: ident(&conv::to_pascal_case(godot_ty)), + } + } + + pub fn description(&self) -> String { + if self.rust_ty == self.godot_ty { + self.godot_ty.clone() + } else { + format!("{} [renamed {}]", self.godot_ty, self.rust_ty) + } + } + + pub fn virtual_trait_name(&self) -> String { + format!("I{}", self.rust_ty) + } +} + +impl ToTokens for TyName { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.rust_ty.to_tokens(tokens) + } +} diff --git a/godot-codegen/src/models/domain_mapping.rs b/godot-codegen/src/models/domain_mapping.rs new file mode 100644 index 000000000..e3d7259dd --- /dev/null +++ b/godot-codegen/src/models/domain_mapping.rs @@ -0,0 +1,603 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +use crate::context::Context; +use crate::models::domain::{ + BuildConfiguration, BuiltinClass, BuiltinMethod, BuiltinSize, BuiltinVariant, Class, + ClassCommons, ClassConstant, ClassConstantValue, ClassMethod, Constructor, Enum, Enumerator, + EnumeratorValue, ExtensionApi, FnDirection, FnParam, FnQualifier, FnReturn, FunctionCommon, + GodotApiVersion, NativeStructure, Operator, Singleton, TyName, UtilityFunction, +}; +use crate::models::json::{ + JsonBuiltinClass, JsonBuiltinMethod, JsonBuiltinSizes, JsonClass, JsonClassConstant, + JsonClassMethod, JsonConstructor, JsonEnum, JsonEnumConstant, JsonExtensionApi, JsonHeader, + JsonMethodReturn, JsonNativeStructure, JsonOperator, JsonSingleton, JsonUtilityFunction, +}; +use crate::util::{get_api_level, ident, option_as_slice}; +use crate::{conv, special_cases, ModName}; +use proc_macro2::Ident; +use std::collections::HashMap; + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Top-level + +impl ExtensionApi { + pub fn from_json(json: &JsonExtensionApi, ctx: &mut Context) -> Self { + Self { + builtins: BuiltinVariant::all_from_json(&json.global_enums, &json.builtin_classes, ctx), + classes: json + .classes + .iter() + .filter_map(|json| Class::from_json(json, ctx)) + .collect(), + singletons: json.singletons.iter().map(Singleton::from_json).collect(), + native_structures: json + .native_structures + .iter() + .map(NativeStructure::from_json) + .collect(), + utility_functions: json + .utility_functions + .iter() + .filter_map(|json| UtilityFunction::from_json(json, ctx)) + .collect(), + global_enums: json + .global_enums + .iter() + .map(|json| Enum::from_json(json, None)) + .collect(), + godot_version: GodotApiVersion::from_json(&json.header), + builtin_sizes: Self::builtin_size_from_json(&json.builtin_class_sizes), + } + } + + fn builtin_size_from_json(json_builtin_sizes: &[JsonBuiltinSizes]) -> Vec { + let mut result = Vec::new(); + + for json_builtin_size in json_builtin_sizes { + let build_config_str = json_builtin_size.build_configuration.as_str(); + let config = BuildConfiguration::from_json(build_config_str); + + if config.is_applicable() { + for size_for_config in &json_builtin_size.sizes { + result.push(BuiltinSize { + builtin_original_name: size_for_config.name.clone(), + config, + size: size_for_config.size, + }); + } + } + } + + result + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Builtins + classes + singletons + +impl Class { + pub fn from_json(json: &JsonClass, ctx: &mut Context) -> Option { + let ty_name = TyName::from_godot(&json.name); + if special_cases::is_class_deleted(&ty_name) { + return None; + } + + let mod_name = ModName::from_godot(&ty_name.godot_ty); + + let constants = option_as_slice(&json.constants) + .iter() + .map(ClassConstant::from_json) + .collect(); + + let enums = option_as_slice(&json.enums) + .iter() + .map(|e| { + let surrounding_class = Some(&ty_name); + Enum::from_json(e, surrounding_class) + }) + .collect(); + + let methods = option_as_slice(&json.methods) + .iter() + .filter_map(|m| { + let surrounding_class = &ty_name; + ClassMethod::from_json(m, surrounding_class, ctx) + }) + .collect(); + + Some(Self { + common: ClassCommons { + name: ty_name, + mod_name, + }, + is_refcounted: json.is_refcounted, + is_instantiable: json.is_instantiable, + inherits: json.inherits.clone(), + api_level: get_api_level(json), + constants, + enums, + methods, + }) + } +} + +impl BuiltinClass { + pub fn from_json(json: &JsonBuiltinClass, ctx: &mut Context) -> Option { + let ty_name = TyName::from_godot(&json.name); + + if special_cases::is_builtin_type_deleted(&ty_name) { + return None; + } + + let inner_name = TyName::from_godot(&format!("Inner{}", ty_name.godot_ty)); + let mod_name = ModName::from_godot(&ty_name.godot_ty); + + let operators = json.operators.iter().map(Operator::from_json).collect(); + + let methods = option_as_slice(&json.methods) + .iter() + .filter_map(|m| { + let inner_class_name = &ty_name; + BuiltinMethod::from_json(m, &ty_name, inner_class_name, ctx) + }) + .collect(); + + let constructors = json + .constructors + .iter() + .map(Constructor::from_json) + .collect(); + + let has_destructor = json.has_destructor; + + let enums = option_as_slice(&json.enums) + .iter() + .map(|e| { + let surrounding_class = Some(&ty_name); + Enum::from_json(&e.to_enum(), surrounding_class) + }) + .collect(); + + Some(Self { + common: ClassCommons { + name: ty_name, + mod_name, + }, + inner_name, + operators, + methods, + constructors, + has_destructor, + enums, + }) + } +} + +impl Singleton { + pub fn from_json(json: &JsonSingleton) -> Self { + Self { + name: TyName::from_godot(&json.name), + } + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Builtin variants + +impl BuiltinVariant { + /// Returns all builtins, ordered by enum ordinal value. + pub fn all_from_json( + global_enums: &[JsonEnum], + builtin_classes: &[JsonBuiltinClass], + ctx: &mut Context, + ) -> Vec { + fn normalize(name: &str) -> String { + name.to_ascii_lowercase().replace('_', "") + } + + let variant_type_enum = global_enums + .iter() + .find(|e| &e.name == "Variant.Type") + .expect("missing enum for VariantType in JSON"); + + // Make HashMap from builtin_classes, keyed by a normalized version of their names (all-lower, no underscores) + let builtin_classes: HashMap = builtin_classes + .iter() + .map(|c| (normalize(&c.name), c)) + .collect(); + + let mut all = variant_type_enum + .values + .iter() + .filter_map(|e| { + let json_shout_case = e + .name + .strip_prefix("TYPE_") + .expect("variant enumerator lacks prefix 'TYPE_'"); + + if json_shout_case == "NIL" || json_shout_case == "MAX" { + return None; + } + + let name = normalize(json_shout_case); + let json_builtin_class = builtin_classes.get(&name).copied(); + let json_ord = e.to_enum_ord(); + + Some(Self::from_json( + json_shout_case, + json_ord, + json_builtin_class, + ctx, + )) + }) + .collect::>(); + + all.sort_by_key(|v| v.variant_type_ord); + all + } + + pub fn from_json( + json_variant_enumerator_name: &str, + json_variant_enumerator_ord: i32, + json_builtin_class: Option<&JsonBuiltinClass>, + ctx: &mut Context, + ) -> Self { + let builtin_class; + let godot_original_name; + + // Nil, int, float etc. are not represented by a BuiltinVariant. + // Object has no BuiltinClass, but still gets its BuiltinVariant instance. + if let Some(json_builtin) = json_builtin_class { + builtin_class = BuiltinClass::from_json(json_builtin, ctx); + godot_original_name = json_builtin.name.clone(); + } else { + assert_eq!(json_variant_enumerator_name, "OBJECT"); + + builtin_class = None; + godot_original_name = "Object".to_string(); + }; + + Self { + godot_original_name, + godot_shout_name: json_variant_enumerator_name.to_string(), // Without `TYPE_` prefix. + godot_snake_name: conv::to_snake_case(json_variant_enumerator_name), + builtin_class, + variant_type_ord: json_variant_enumerator_ord, + } + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Constructors, operators + +impl Constructor { + pub fn from_json(json: &JsonConstructor) -> Self { + Self { + index: json.index, // TODO use enum for Default/Copy/Other(index) + raw_parameters: json.arguments.as_ref().map_or(vec![], |vec| vec.clone()), + } + } +} + +impl Operator { + pub fn from_json(json: &JsonOperator) -> Self { + Self { + symbol: json.name.clone(), + } + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Build config + version + +impl BuildConfiguration { + pub fn from_json(json: &str) -> Self { + match json { + "float_32" => Self::Float32, + "float_64" => Self::Float64, + "double_32" => Self::Double32, + "double_64" => Self::Double64, + _ => panic!("invalid build configuration: {}", json), + } + } +} + +impl GodotApiVersion { + pub fn from_json(json: &JsonHeader) -> Self { + let version_string = json + .version_full_name + .strip_prefix("Godot Engine ") + .unwrap_or(&json.version_full_name) + .to_string(); + + Self { + major: json.version_major, + minor: json.version_minor, + patch: json.version_patch, + version_string, + } + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Functions + +impl BuiltinMethod { + pub fn from_json( + method: &JsonBuiltinMethod, + builtin_name: &TyName, + inner_class_name: &TyName, + ctx: &mut Context, + ) -> Option { + if special_cases::is_builtin_method_deleted(builtin_name, method) { + return None; + } + + let return_value = method + .return_type + .as_deref() + .map(JsonMethodReturn::from_type_no_meta); + + // use BuiltinMethod, but adopt values from above FnSignature expr + Some(Self { + common: FunctionCommon { + // Fill in these fields + name: method.name.clone(), + godot_name: method.name.clone(), + // Disable default parameters for builtin classes. + // They are not public-facing and need more involved implementation (lifetimes etc). Also reduces number of symbols in API. + parameters: FnParam::new_range_no_defaults(&method.arguments, ctx), + return_value: FnReturn::new(&return_value, ctx), + is_vararg: method.is_vararg, + is_private: special_cases::is_method_private(builtin_name, &method.name), + direction: FnDirection::Outbound { + hash: method.hash.expect("hash absent for builtin method"), + }, + }, + qualifier: FnQualifier::from_const_static(method.is_const, method.is_static), + surrounding_class: inner_class_name.clone(), + }) + } +} + +impl ClassMethod { + pub fn from_json( + method: &JsonClassMethod, + class_name: &TyName, + ctx: &mut Context, + ) -> Option { + assert!(!special_cases::is_class_deleted(class_name)); + + if special_cases::is_class_method_deleted(class_name, method, ctx) { + return None; + } + + if method.is_virtual { + Self::from_json_virtual(method, class_name, ctx) + } else { + Self::from_json_outbound(method, class_name, ctx) + } + } + + fn from_json_outbound( + method: &JsonClassMethod, + class_name: &TyName, + ctx: &mut Context, + ) -> Option { + assert!(!method.is_virtual); + let hash = method + .hash + .expect("hash absent for non-virtual class method"); + + let rust_method_name = special_cases::maybe_rename_class_method(class_name, &method.name); + + Self::from_json_inner( + method, + rust_method_name, + class_name, + FnDirection::Outbound { hash }, + ctx, + ) + } + + fn from_json_virtual( + method: &JsonClassMethod, + class_name: &TyName, + ctx: &mut Context, + ) -> Option { + assert!(method.is_virtual); + assert!( + method.hash.is_none(), + "hash present for virtual class method" + ); + + let rust_method_name = Self::make_virtual_method_name(&method.name); + + Self::from_json_inner( + method, + rust_method_name, + class_name, + FnDirection::Virtual, + ctx, + ) + } + + fn from_json_inner( + method: &JsonClassMethod, + rust_method_name: &str, + class_name: &TyName, + direction: FnDirection, + ctx: &mut Context, + ) -> Option { + if special_cases::is_class_method_deleted(class_name, method, ctx) { + return None; + } + + let is_private = special_cases::is_method_private(class_name, &method.name); + + let godot_method_name = method.name.clone(); + + let qualifier = { + // Override const-qualification for known special cases (FileAccess::get_16, StreamPeer::get_u16, etc.). + let mut is_actually_const = method.is_const; + if let Some(override_const) = special_cases::is_class_method_const(class_name, method) { + is_actually_const = override_const; + } + + FnQualifier::from_const_static(is_actually_const, method.is_static) + }; + + Some(Self { + common: FunctionCommon { + name: rust_method_name.to_string(), + godot_name: godot_method_name, + parameters: FnParam::new_range(&method.arguments, ctx), + return_value: FnReturn::new(&method.return_value, ctx), + is_vararg: method.is_vararg, + is_private, + direction, + }, + qualifier, + surrounding_class: class_name.clone(), + }) + } + + fn make_virtual_method_name(godot_method_name: &str) -> &str { + // Remove leading underscore from virtual method names. + let method_name = godot_method_name + .strip_prefix('_') + .unwrap_or(godot_method_name); + + special_cases::maybe_rename_virtual_method(method_name) + } +} + +impl UtilityFunction { + pub fn from_json(function: &JsonUtilityFunction, ctx: &mut Context) -> Option { + if special_cases::is_utility_function_deleted(function, ctx) { + return None; + } + + let godot_method_name = function.name.clone(); + let rust_method_name = godot_method_name.clone(); // No change for now. + + let return_value = function + .return_type + .as_deref() + .map(JsonMethodReturn::from_type_no_meta); + + Some(Self { + common: FunctionCommon { + name: rust_method_name, + godot_name: godot_method_name, + parameters: FnParam::new_range(&function.arguments, ctx), + return_value: FnReturn::new(&return_value, ctx), + is_vararg: function.is_vararg, + is_private: false, + direction: FnDirection::Outbound { + hash: function.hash, + }, + }, + }) + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Enums + enumerator constants + +impl Enum { + pub fn from_json(json_enum: &JsonEnum, surrounding_class: Option<&TyName>) -> Self { + let godot_name = &json_enum.name; + let is_bitfield = json_enum.is_bitfield; + + let rust_enum_name = conv::make_enum_name_str(godot_name); + let rust_enumerator_names = { + let godot_enumerator_names = json_enum.values.iter().map(|e| e.name.as_str()).collect(); + let godot_class_name = surrounding_class.as_ref().map(|ty| ty.godot_ty.as_str()); + + conv::make_enumerator_names(godot_class_name, &rust_enum_name, godot_enumerator_names) + }; + + let enumerators = json_enum + .values + .iter() + .zip(rust_enumerator_names) + .map(|(json_constant, rust_name)| { + Enumerator::from_json(json_constant, rust_name, is_bitfield) + }) + .collect(); + + Self { + name: ident(&rust_enum_name), + godot_name: godot_name.clone(), + is_bitfield, + enumerators, + } + } +} + +impl Enumerator { + pub fn from_json(json: &JsonEnumConstant, rust_name: Ident, is_bitfield: bool) -> Self { + let value = if is_bitfield { + let ord = json.value.try_into().unwrap_or_else(|_| { + panic!( + "bitfield value {} = {} is negative; please report this", + json.name, json.value + ) + }); + + EnumeratorValue::Bitfield(ord) + } else { + let ord = json.value.try_into().unwrap_or_else(|_| { + panic!( + "enum value {} = {} is out of range for i32; please report this", + json.name, json.value + ) + }); + + EnumeratorValue::Enum(ord) + }; + + Self { + name: rust_name, + godot_name: json.name.clone(), + value, + } + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Constants + +impl ClassConstant { + pub fn from_json(json: &JsonClassConstant) -> Self { + // Godot types only use i32, but other extensions may have i64. Use smallest possible type. + let value = if let Ok(i32_value) = i32::try_from(json.value) { + ClassConstantValue::I32(i32_value) + } else { + ClassConstantValue::I64(json.value) + }; + + Self { + name: json.name.clone(), + value, + } + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Native structures + +impl NativeStructure { + pub fn from_json(json: &JsonNativeStructure) -> Self { + Self { + name: json.name.clone(), + format: json.format.clone(), + } + } +} diff --git a/godot-codegen/src/api_parser.rs b/godot-codegen/src/models/json.rs similarity index 62% rename from godot-codegen/src/api_parser.rs rename to godot-codegen/src/models/json.rs index 3d7625f90..bb1a10ad9 100644 --- a/godot-codegen/src/api_parser.rs +++ b/godot-codegen/src/models/json.rs @@ -6,7 +6,8 @@ */ // TODO remove this warning once impl is complete -#![allow(dead_code)] +// Several types have #[allow(dead_code)], can be subsequently removed. + #![allow(clippy::question_mark)] // in #[derive(DeJson)] use nanoserde::DeJson; @@ -15,19 +16,19 @@ use nanoserde::DeJson; // JSON models #[derive(DeJson)] -pub struct ExtensionApi { - pub header: Header, - pub builtin_class_sizes: Vec, - pub builtin_classes: Vec, - pub classes: Vec, - pub global_enums: Vec, - pub utility_functions: Vec, - pub native_structures: Vec, - pub singletons: Vec, +pub struct JsonExtensionApi { + pub header: JsonHeader, + pub builtin_class_sizes: Vec, + pub builtin_classes: Vec, + pub classes: Vec, + pub global_enums: Vec, + pub utility_functions: Vec, + pub native_structures: Vec, + pub singletons: Vec, } #[derive(DeJson, Clone, Debug)] -pub struct Header { +pub struct JsonHeader { pub version_major: u8, pub version_minor: u8, pub version_patch: u8, @@ -37,53 +38,53 @@ pub struct Header { } #[derive(DeJson)] -pub struct ClassSizes { +pub struct JsonBuiltinSizes { pub build_configuration: String, - pub sizes: Vec, + pub sizes: Vec, } #[derive(DeJson)] -pub struct ClassSize { +pub struct JsonBuiltinSizeForConfig { pub name: String, pub size: usize, } #[derive(DeJson)] -pub struct BuiltinClass { +pub struct JsonBuiltinClass { pub name: String, pub indexing_return_type: Option, pub is_keyed: bool, - pub members: Option>, + // pub members: Option>, // pub constants: Option>, - pub enums: Option>, // no bitfield - pub operators: Vec, - pub methods: Option>, - pub constructors: Vec, + pub enums: Option>, // no bitfield + pub operators: Vec, + pub methods: Option>, + pub constructors: Vec, pub has_destructor: bool, } #[derive(DeJson)] -pub struct Class { +pub struct JsonClass { pub name: String, pub is_refcounted: bool, pub is_instantiable: bool, pub inherits: Option, pub api_type: String, - pub constants: Option>, - pub enums: Option>, - pub methods: Option>, + pub constants: Option>, + pub enums: Option>, + pub methods: Option>, // pub properties: Option>, // pub signals: Option>, } #[derive(DeJson)] -pub struct NativeStructure { +pub struct JsonNativeStructure { pub name: String, pub format: String, } #[derive(DeJson)] -pub struct Singleton { +pub struct JsonSingleton { pub name: String, // Note: `type` currently has always same value as `name`, thus redundant // #[nserde(rename = "type")] @@ -91,21 +92,21 @@ pub struct Singleton { } #[derive(DeJson)] -pub struct Enum { +pub struct JsonEnum { pub name: String, pub is_bitfield: bool, - pub values: Vec, + pub values: Vec, } #[derive(DeJson)] -pub struct BuiltinClassEnum { +pub struct JsonBuiltinEnum { pub name: String, - pub values: Vec, + pub values: Vec, } -impl BuiltinClassEnum { - pub(crate) fn to_enum(&self) -> Enum { - Enum { +impl JsonBuiltinEnum { + pub fn to_enum(&self) -> JsonEnum { + JsonEnum { name: self.name.clone(), is_bitfield: false, values: self.values.clone(), @@ -114,7 +115,7 @@ impl BuiltinClassEnum { } #[derive(DeJson, Clone)] -pub struct EnumConstant { +pub struct JsonEnumConstant { pub name: String, // i64 is common denominator for enum, bitfield and constant values. @@ -122,12 +123,7 @@ pub struct EnumConstant { pub value: i64, } -pub enum ConstValue { - I32(i32), - I64(i64), -} - -impl EnumConstant { +impl JsonEnumConstant { pub fn to_enum_ord(&self) -> i32 { self.value.try_into().unwrap_or_else(|_| { panic!( @@ -136,31 +132,14 @@ impl EnumConstant { ) }) } - - pub fn to_bitfield_ord(&self) -> u64 { - self.value.try_into().unwrap_or_else(|_| { - panic!( - "bitfield value {} = {} is negative, please report this", - self.name, self.value - ) - }) - } - - pub fn to_constant(&self) -> ConstValue { - if let Ok(value) = i32::try_from(self.value) { - ConstValue::I32(value) - } else { - ConstValue::I64(self.value) - } - } } -pub type ClassConstant = EnumConstant; +pub type JsonClassConstant = JsonEnumConstant; /* // Constants of builtin types have a string value like "Vector2(1, 1)", hence also a type field #[derive(DeJson)] -pub struct BuiltinConstant { +pub struct JsonBuiltinConstant { pub name: String, #[nserde(rename = "type")] pub type_: String, @@ -169,21 +148,22 @@ pub struct BuiltinConstant { */ #[derive(DeJson)] -pub struct Operator { +pub struct JsonOperator { pub name: String, pub right_type: Option, // null if unary pub return_type: String, } #[derive(DeJson)] -pub struct Member { +pub struct JsonMember { pub name: String, #[nserde(rename = "type")] pub type_: String, } #[derive(DeJson)] -pub struct Property { +#[allow(dead_code)] +pub struct JsonProperty { #[nserde(rename = "type")] type_: String, name: String, @@ -193,55 +173,57 @@ pub struct Property { } #[derive(DeJson)] -pub struct Signal { +#[allow(dead_code)] +pub struct JsonSignal { name: String, - arguments: Option>, + arguments: Option>, } #[derive(DeJson)] -pub struct Constructor { +pub struct JsonConstructor { pub index: usize, - pub arguments: Option>, + pub arguments: Option>, } #[derive(DeJson)] -pub struct UtilityFunction { +pub struct JsonUtilityFunction { pub name: String, pub return_type: Option, + /// `"general"` or `"math"` pub category: String, pub is_vararg: bool, pub hash: i64, - pub arguments: Option>, + pub arguments: Option>, } #[derive(DeJson)] -pub struct BuiltinClassMethod { +pub struct JsonBuiltinMethod { pub name: String, pub return_type: Option, pub is_vararg: bool, pub is_const: bool, pub is_static: bool, pub hash: Option, - pub arguments: Option>, + pub arguments: Option>, } #[derive(DeJson, Clone)] -pub struct ClassMethod { +pub struct JsonClassMethod { pub name: String, pub is_const: bool, pub is_vararg: bool, pub is_static: bool, pub is_virtual: bool, pub hash: Option, - pub return_value: Option, - pub arguments: Option>, + pub return_value: Option, + pub arguments: Option>, } // Example: set_point_weight_scale -> // [ {name: "id", type: "int", meta: "int64"}, // {name: "weight_scale", type: "float", meta: "float"}, #[derive(DeJson, Clone)] -pub struct MethodArg { +pub struct JsonMethodArg { pub name: String, #[nserde(rename = "type")] pub type_: String, @@ -251,13 +233,13 @@ pub struct MethodArg { // Example: get_available_point_id -> {type: "int", meta: "int64"} #[derive(DeJson, Clone)] -pub struct MethodReturn { +pub struct JsonMethodReturn { #[nserde(rename = "type")] pub type_: String, pub meta: Option, } -impl MethodReturn { +impl JsonMethodReturn { pub fn from_type_no_meta(type_: &str) -> Self { Self { type_: type_.to_owned(), @@ -269,22 +251,7 @@ impl MethodReturn { // ---------------------------------------------------------------------------------------------------------------------------------------------- // Implementation -pub fn load_extension_api( - watch: &mut godot_bindings::StopWatch, -) -> (ExtensionApi, [&'static str; 2]) { - // For float/double inference, see: - // * https://github.com/godotengine/godot-proposals/issues/892 - // * https://github.com/godotengine/godot-cpp/pull/728 - // Have to do target_pointer_width check after code generation - // So pass a [32bit, 64bit] around of appropriate precision - // For why see: https://github.com/rust-lang/rust/issues/42587 - let build_config: [&'static str; 2] = { - if cfg!(feature = "double-precision") { - ["double_32", "double_64"] - } else { - ["float_32", "float_64"] - } - }; +pub fn load_extension_api(watch: &mut godot_bindings::StopWatch) -> JsonExtensionApi { // Use type inference, so we can accept both String (dynamically resolved) and &str (prebuilt). // #[allow]: as_ref() acts as impl AsRef, but with conditional compilation @@ -292,11 +259,10 @@ pub fn load_extension_api( #[allow(clippy::useless_asref)] let json_str: &str = json.as_ref(); - let model: ExtensionApi = + let model: JsonExtensionApi = DeJson::deserialize_json(json_str).expect("failed to deserialize JSON"); watch.record("deserialize_json"); println!("Parsed extension_api.json for version {:?}", model.header); - - (model, build_config) + model } diff --git a/godot-codegen/src/models/mod.rs b/godot-codegen/src/models/mod.rs new file mode 100644 index 000000000..f7f24c737 --- /dev/null +++ b/godot-codegen/src/models/mod.rs @@ -0,0 +1,11 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +pub mod domain; +pub mod domain_mapping; +pub mod json; +pub mod tokens; diff --git a/godot-codegen/src/models/tokens.rs b/godot-codegen/src/models/tokens.rs new file mode 100644 index 000000000..072c2d5d1 --- /dev/null +++ b/godot-codegen/src/models/tokens.rs @@ -0,0 +1,6 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ diff --git a/godot-codegen/src/special_cases.rs b/godot-codegen/src/special_cases.rs index d5745624d..3ee3a0658 100644 --- a/godot-codegen/src/special_cases.rs +++ b/godot-codegen/src/special_cases.rs @@ -23,13 +23,14 @@ #![allow(clippy::match_like_matches_macro)] // if there is only one rule -use crate::api_parser::{BuiltinClassMethod, ClassMethod}; +use crate::codegen_special_cases; +use crate::models::domain::TyName; +use crate::models::json::{JsonBuiltinMethod, JsonClassMethod, JsonUtilityFunction}; use crate::Context; -use crate::{codegen_special_cases, TyName}; #[rustfmt::skip] -pub(crate) fn is_class_method_deleted(class_name: &TyName, method: &ClassMethod, ctx: &mut Context) -> bool { - if codegen_special_cases::is_class_method_excluded(method, false, ctx){ +pub(crate) fn is_class_method_deleted(class_name: &TyName, method: &JsonClassMethod, ctx: &mut Context) -> bool { + if codegen_special_cases::is_class_method_excluded(method, ctx){ return true; } @@ -48,14 +49,18 @@ pub(crate) fn is_class_method_deleted(class_name: &TyName, method: &ClassMethod, } } -#[rustfmt::skip] pub(crate) fn is_class_deleted(class_name: &TyName) -> bool { + codegen_special_cases::is_class_excluded(&class_name.godot_ty) + || is_godot_type_deleted(class_name) +} + +pub(crate) fn is_godot_type_deleted(ty_name: &TyName) -> bool { // Exclude experimental APIs unless opted-in. - if !cfg!(feature = "experimental-godot-api") && is_class_experimental(class_name) { + if !cfg!(feature = "experimental-godot-api") && is_class_experimental(ty_name) { return true; } - let class_name = class_name.godot_ty.as_str(); + let class_name = ty_name.godot_ty.as_str(); // OpenXR has not been available for macOS before 4.2. // See e.g. https://github.com/GodotVR/godot-xr-tools/issues/479. @@ -195,7 +200,7 @@ pub(crate) fn is_method_excluded_from_default_params(class_name: Option<&TyName> /// should be returned to take precedence over general rules. Example: `FileAccess::get_pascal_string()` is mut, but would be const-qualified /// since it looks like a getter. #[rustfmt::skip] -pub(crate) fn is_class_method_const(class_name: &TyName, godot_method: &ClassMethod) -> Option { +pub(crate) fn is_class_method_const(class_name: &TyName, godot_method: &JsonClassMethod) -> Option { match (class_name.godot_ty.as_str(), godot_method.name.as_str()) { // Changed to const. | ("Object", "to_string") @@ -223,13 +228,18 @@ pub(crate) fn is_class_method_const(class_name: &TyName, godot_method: &ClassMet | ("StreamPeer", "get_double") => Some(false), */ + + _ => { + // TODO Many getters are mutably qualified (GltfAccessor::get_max, CameraAttributes::get_exposure_multiplier, ...). + // As a default, set those to const. - _ => None, + None + }, } } /// True if builtin method is excluded. Does NOT check for type exclusion; use [`is_builtin_type_deleted`] for that. -pub(crate) fn is_builtin_method_deleted(_class_name: &TyName, method: &BuiltinClassMethod) -> bool { +pub(crate) fn is_builtin_method_deleted(_class_name: &TyName, method: &JsonBuiltinMethod) -> bool { // Currently only deleted if codegen. codegen_special_cases::is_builtin_method_excluded(method) } @@ -245,6 +255,13 @@ pub(crate) fn is_builtin_type_scalar(name: &str) -> bool { name.chars().next().unwrap().is_ascii_lowercase() } +pub(crate) fn is_utility_function_deleted( + function: &JsonUtilityFunction, + ctx: &mut Context, +) -> bool { + codegen_special_cases::is_utility_function_excluded(function, ctx) +} + pub(crate) fn maybe_rename_class_method<'m>( class_name: &TyName, godot_method_name: &'m str, @@ -255,3 +272,13 @@ pub(crate) fn maybe_rename_class_method<'m>( _ => godot_method_name, } } + +// Maybe merge with above? +pub(crate) fn maybe_rename_virtual_method(rust_method_name: &str) -> &str { + // A few classes define a virtual method called "_init" (distinct from the constructor) + // -> rename those to avoid a name conflict in I* interface trait. + match rust_method_name { + "init" => "init_ext", + _ => rust_method_name, + } +} diff --git a/godot-codegen/src/tests.rs b/godot-codegen/src/tests.rs index 124af9781..c1f9c4196 100644 --- a/godot-codegen/src/tests.rs +++ b/godot-codegen/src/tests.rs @@ -80,7 +80,7 @@ fn test_snake_conversion() { ("JSON", "json"), ("JSONParseResult", "json_parse_result"), ("JSONRPC", "json_rpc"), - ("NetworkedMultiplayerENet", "networked_multiplayer_e_net"), + ("NetworkedMultiplayerENet", "networked_multiplayer_enet"), ("ObjectID", "object_id"), ("OpenXRAPIExtension", "open_xr_api_extension"), ("OpenXRIPBinding", "open_xr_ip_binding"), diff --git a/godot-codegen/src/util.rs b/godot-codegen/src/util.rs index fe0dce90b..e7bfcbf08 100644 --- a/godot-codegen/src/util.rs +++ b/godot-codegen/src/util.rs @@ -5,14 +5,16 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::api_parser::{ - BuiltinClassMethod, Class, ClassConstant, ClassMethod, ConstValue, Enum, UtilityFunction, -}; -use crate::{conv, RustTy, TyName}; +use crate::conv; +use crate::models::json::{JsonClass, JsonClassConstant}; use proc_macro2::{Ident, Literal, TokenStream}; use quote::{format_ident, quote}; +use crate::models::domain::{ + BuiltinClass, BuiltinMethod, Class, ClassConstant, ClassConstantValue, ClassLike, ClassMethod, + Enum, Enumerator, EnumeratorValue, Function, RustTy, TyName, +}; use std::fmt; #[derive(Clone, Eq, PartialEq, Debug)] @@ -77,7 +79,7 @@ impl ClassCodegenLevel { // Could potentially save a lot of string allocations with lifetimes. // See also crate::lazy_keys. #[derive(Eq, PartialEq, Hash)] -pub(crate) enum MethodTableKey { +pub enum MethodTableKey { ClassMethod { api_level: ClassCodegenLevel, class_ty: TyName, @@ -97,6 +99,21 @@ pub(crate) enum MethodTableKey { } impl MethodTableKey { + pub fn from_class(class: &Class, method: &ClassMethod) -> MethodTableKey { + Self::ClassMethod { + api_level: class.api_level, + class_ty: class.name().clone(), + method_name: method.godot_name().to_string(), + } + } + + pub fn from_builtin(builtin_class: &BuiltinClass, method: &BuiltinMethod) -> MethodTableKey { + Self::BuiltinMethod { + builtin_ty: builtin_class.name().clone(), + method_name: method.godot_name().to_string(), + } + } + /// Maps the method table key to a "category", meaning a distinct method table. /// /// Categories have independent address spaces for indices, meaning they begin again at 0 for each new category. @@ -161,27 +178,16 @@ pub(crate) fn make_imports() -> TokenStream { } // Use &ClassMethod instead of &str, to make sure it's the original Godot name and no rename. -pub(crate) fn make_class_method_ptr_name(class_ty: &TyName, method: &ClassMethod) -> Ident { +pub(crate) fn make_table_accessor_name(class_ty: &TyName, method: &dyn Function) -> Ident { format_ident!( "{}__{}", conv::to_snake_case(&class_ty.godot_ty), - method.name - ) -} - -pub(crate) fn make_builtin_method_ptr_name( - builtin_ty: &TyName, - method: &BuiltinClassMethod, -) -> Ident { - format_ident!( - "{}__{}", - conv::to_snake_case(&builtin_ty.godot_ty), - method.name + method.godot_name() ) } -pub(crate) fn make_utility_function_ptr_name(function: &UtilityFunction) -> Ident { - safe_ident(&function.name) +pub(crate) fn make_utility_function_ptr_name(godot_function_name: &str) -> Ident { + safe_ident(godot_function_name) } #[cfg(since_api = "4.2")] @@ -204,7 +210,7 @@ pub fn make_sname_ptr(identifier: &str) -> TokenStream { } } -pub fn get_api_level(class: &Class) -> ClassCodegenLevel { +pub fn get_api_level(class: &JsonClass) -> ClassCodegenLevel { // Work around wrong classification in https://github.com/godotengine/godot/issues/86206. fn override_editor(class_name: &str) -> bool { cfg!(before_api = "4.3") @@ -228,71 +234,44 @@ pub fn get_api_level(class: &Class) -> ClassCodegenLevel { } } -pub fn make_enum_definition(enum_: &Enum, class_name: Option<&str>) -> TokenStream { +pub fn make_enum_definition(enum_: &Enum) -> TokenStream { // TODO enums which have unique ords could be represented as Rust enums // This would allow exhaustive matches (or at least auto-completed matches + #[non_exhaustive]). But even without #[non_exhaustive], // this might be a forward compatibility hazard, if Godot deprecates enumerators and adds new ones with existing ords. - let enum_name_str = conv::make_enum_name_str(&enum_.name); - let enum_name = ident(&enum_name_str); + let rust_enum_name = &enum_.name; // TODO remove once deprecated is removed. - let deprecated_enum_decl = if enum_name != enum_.name { - let deprecated_enum_name = ident(&enum_.name); - let msg = format!("Renamed to `{enum_name}`."); + let deprecated_enum_decl = if rust_enum_name != enum_.godot_name.as_str() { + let deprecated_enum_name = ident(&enum_.godot_name); + let msg = format!("Renamed to `{rust_enum_name}`."); quote! { #[deprecated = #msg] - pub type #deprecated_enum_name = #enum_name; + pub type #deprecated_enum_name = #rust_enum_name; } } else { TokenStream::new() }; - let godot_enumerators = &enum_.values; - let mut enumerators = Vec::with_capacity(godot_enumerators.len()); + let rust_enumerators = &enum_.enumerators; + + let mut enumerators = Vec::with_capacity(rust_enumerators.len()); let mut deprecated_enumerators = Vec::new(); // This is only used for enum ords (i32), not bitfield flags (u64). - let mut unique_ords = Vec::with_capacity(godot_enumerators.len()); - - let rust_enumerator_names = { - let original_enumerator_names = enum_.values.iter().map(|e| e.name.as_str()).collect(); - conv::make_enumerator_names(class_name, &enum_name_str, original_enumerator_names) - }; - - for (enumerator, enumerator_name) in godot_enumerators.iter().zip(rust_enumerator_names) { - let ordinal_lit = if enum_.is_bitfield { - let bitfield_ord: u64 = enumerator.to_bitfield_ord(); - make_bitfield_flag_ord(bitfield_ord) - } else { - let enum_ord: i32 = enumerator.to_enum_ord(); - unique_ords.push(enum_ord); - make_enumerator_ord(enum_ord) - }; - - let godot_name_str = &enumerator.name; - let doc_alias = if enumerator_name == godot_name_str { - TokenStream::new() - } else { - // Godot and Rust names differ -> add doc alias for searchability. - let msg = format!("Renamed to `{}`.", enumerator_name); - let deprecated_ident = ident(godot_name_str); + let mut unique_ords = Vec::with_capacity(rust_enumerators.len()); - // For now, list previous identifier at the end. - deprecated_enumerators.push(quote! { - #[deprecated = #msg] - pub const #deprecated_ident: Self = Self { ord: #ordinal_lit }; - }); + for enumerator in rust_enumerators.iter() { + let (def, deprecated_def) = make_enumerator_definition(enumerator); + enumerators.push(def); - quote! { - #[doc(alias = #godot_name_str)] - } - }; + if let Some(def) = deprecated_def { + deprecated_enumerators.push(def); + } - enumerators.push(quote! { - #doc_alias - pub const #enumerator_name: Self = Self { ord: #ordinal_lit }; - }); + if let EnumeratorValue::Enum(ord) = enumerator.value { + unique_ords.push(ord); + } } enumerators.extend(deprecated_enumerators); @@ -312,7 +291,7 @@ pub fn make_enum_definition(enum_: &Enum, class_name: Option<&str>) -> TokenStre // Enums implement IndexEnum only if they are "index-like" (see docs). if let Some(enum_max) = try_count_index_enum(enum_) { quote! { - impl crate::obj::IndexEnum for #enum_name { + impl crate::obj::IndexEnum for #rust_enum_name { const ENUMERATOR_COUNT: usize = #enum_max; } } @@ -331,7 +310,7 @@ pub fn make_enum_definition(enum_: &Enum, class_name: Option<&str>) -> TokenStre // impl #enum_name { // pub const UNSET: Self = Self { ord: 0 }; // } - impl std::ops::BitOr for #enum_name { + impl std::ops::BitOr for #rust_enum_name { type Output = Self; fn bitor(self, rhs: Self) -> Self::Output { @@ -342,7 +321,7 @@ pub fn make_enum_definition(enum_: &Enum, class_name: Option<&str>) -> TokenStre enum_ord_type = quote! { u64 }; self_as_trait = quote! { }; engine_impl = quote! { - impl crate::obj::EngineBitfield for #enum_name { + impl crate::obj::EngineBitfield for #rust_enum_name { fn try_from_ord(ord: u64) -> Option { Some(Self { ord }) } @@ -361,7 +340,7 @@ pub fn make_enum_definition(enum_: &Enum, class_name: Option<&str>) -> TokenStre enum_ord_type = quote! { i32 }; self_as_trait = quote! { }; engine_impl = quote! { - impl crate::obj::EngineEnum for #enum_name { + impl crate::obj::EngineEnum for #rust_enum_name { fn try_from_ord(ord: i32) -> Option { match ord { #( ord @ #unique_ords )|* => Some(Self { ord }), @@ -384,10 +363,10 @@ pub fn make_enum_definition(enum_: &Enum, class_name: Option<&str>) -> TokenStre #[repr(transparent)] #[derive(#( #derives ),*)] - pub struct #enum_name { + pub struct #rust_enum_name { ord: #enum_ord_type } - impl #enum_name { + impl #rust_enum_name { #( #enumerators )* @@ -397,17 +376,17 @@ pub fn make_enum_definition(enum_: &Enum, class_name: Option<&str>) -> TokenStre #index_enum_impl #bitfield_ops - impl crate::builtin::meta::GodotConvert for #enum_name { + impl crate::builtin::meta::GodotConvert for #rust_enum_name { type Via = #enum_ord_type; } - impl crate::builtin::meta::ToGodot for #enum_name { + impl crate::builtin::meta::ToGodot for #rust_enum_name { fn to_godot(&self) -> Self::Via { #self_as_trait::ord(*self) } } - impl crate::builtin::meta::FromGodot for #enum_name { + impl crate::builtin::meta::FromGodot for #rust_enum_name { fn try_from_godot(via: Self::Via) -> std::result::Result { #self_as_trait::try_from_ord(via) .ok_or_else(|| crate::builtin::meta::FromGodotError::InvalidEnum.into_error(via)) @@ -416,22 +395,61 @@ pub fn make_enum_definition(enum_: &Enum, class_name: Option<&str>) -> TokenStre } } +fn make_enumerator_definition(enumerator: &Enumerator) -> (TokenStream, Option) { + let ordinal_lit = match enumerator.value { + EnumeratorValue::Enum(ord) => make_enumerator_ord(ord), + EnumeratorValue::Bitfield(ord) => make_bitfield_flag_ord(ord), + }; + + let rust_ident = &enumerator.name; + let godot_name_str = &enumerator.godot_name; + + let (doc_alias, deprecated_def); + + if rust_ident == godot_name_str { + deprecated_def = None; + doc_alias = TokenStream::new(); + } else { + // Godot and Rust names differ -> add doc alias for searchability. + let msg = format!("Renamed to `{rust_ident}`."); + let deprecated_ident = ident(godot_name_str); + + // For now, list previous identifier at the end. + deprecated_def = Some(quote! { + #[deprecated = #msg] + pub const #deprecated_ident: Self = Self { ord: #ordinal_lit }; + }); + + doc_alias = quote! { + #[doc(alias = #godot_name_str)] + }; + }; + + let def = quote! { + #doc_alias + pub const #rust_ident: Self = Self { ord: #ordinal_lit }; + }; + + (def, deprecated_def) +} + pub fn make_constant_definition(constant: &ClassConstant) -> TokenStream { - let name = ident(&constant.name); - let vis = if constant.name.starts_with("NOTIFICATION_") { + let constant_name = &constant.name; + let ident = ident(constant_name); + let vis = if constant_name.starts_with("NOTIFICATION_") { quote! { pub(crate) } } else { quote! { pub } }; - match constant.to_constant() { - ConstValue::I32(value) => quote! { #vis const #name: i32 = #value; }, - ConstValue::I64(value) => quote! { #vis const #name: i64 = #value; }, + match constant.value { + ClassConstantValue::I32(value) => quote! { #vis const #ident: i32 = #value; }, + ClassConstantValue::I64(value) => quote! { #vis const #ident: i64 = #value; }, } } /// Tries to interpret the constant as a notification one, and transforms it to a Rust identifier on success. -pub fn try_to_notification(constant: &ClassConstant) -> Option { +pub fn try_to_notification(constant: &JsonClassConstant) -> Option { constant .name .strip_prefix("NOTIFICATION_") @@ -442,14 +460,14 @@ pub fn try_to_notification(constant: &ClassConstant) -> Option { /// /// See `godot::obj::IndexEnum` for what constitutes "indexable". fn try_count_index_enum(enum_: &Enum) -> Option { - if enum_.is_bitfield || enum_.values.is_empty() { + if enum_.is_bitfield || enum_.enumerators.is_empty() { return None; } // Sort by ordinal value. Allocates for every enum in the JSON, but should be OK (most enums are indexable). let enumerators = { - let mut enumerators = enum_.values.clone(); - enumerators.sort_by_key(|v| v.value); + let mut enumerators = enum_.enumerators.iter().collect::>(); + enumerators.sort_by_key(|v| v.value.to_i64()); enumerators }; @@ -457,21 +475,23 @@ fn try_count_index_enum(enum_: &Enum) -> Option { // The presence of "MAX" indicates that Godot devs intended the enum to be used as an index. // The condition is not strictly necessary and could theoretically be relaxed; there would need to be concrete use cases though. let last = enumerators.last().unwrap(); // safe because of is_empty check above. - if !last.name.ends_with("_MAX") { + if !last.godot_name.ends_with("_MAX") { return None; } // The rest of the enumerators must be contiguous and without gaps (duplicates are OK). let mut last_value = 0; for enumerator in enumerators.iter() { - if last_value != enumerator.value && last_value + 1 != enumerator.value { + let e_value = enumerator.value.to_i64(); + + if last_value != e_value && last_value + 1 != e_value { return None; } - last_value = enumerator.value; + last_value = e_value; } - Some(last.value as usize) + Some(last_value as usize) } pub fn ident(s: &str) -> Ident { @@ -564,12 +584,6 @@ pub(crate) fn make_enumerator_ord(ord: i32) -> Literal { Literal::i32_suffixed(ord) } -/// This method is needed for platform-dependent types like raw `VariantOperator`, which can be `i32` or `u32`. -/// Do not suffix them. -pub(crate) fn make_enumerator_ord_unsuffixed(ord: i32) -> Literal { - Literal::i32_unsuffixed(ord) -} - pub(crate) fn make_bitfield_flag_ord(ord: u64) -> Literal { Literal::u64_suffixed(ord) } diff --git a/godot-codegen/src/utilities_generator.rs b/godot-codegen/src/utilities_generator.rs index f8eeabae8..17ab8a4fb 100644 --- a/godot-codegen/src/utilities_generator.rs +++ b/godot-codegen/src/utilities_generator.rs @@ -10,20 +10,19 @@ use std::path::Path; use quote::quote; use crate::class_generator::make_utility_function_definition; -use crate::{api_parser::*, SubmitFn}; -use crate::{util, Context}; +use crate::models::domain::ExtensionApi; +use crate::{util, SubmitFn}; pub(crate) fn generate_utilities_file( api: &ExtensionApi, - ctx: &mut Context, gen_path: &Path, submit_fn: &mut SubmitFn, ) { - // note: category unused -> could be their own mod + // Note: category unused -> could be their own mod. let utility_fn_defs = api .utility_functions .iter() - .map(|utility_fn| make_utility_function_definition(utility_fn, ctx)); + .map(make_utility_function_definition); let imports = util::make_imports(); diff --git a/godot-ffi/src/opaque.rs b/godot-ffi/src/opaque.rs index a713eb615..c0b543259 100644 --- a/godot-ffi/src/opaque.rs +++ b/godot-ffi/src/opaque.rs @@ -12,6 +12,13 @@ /// /// Note: due to `align(4)` / `align(8)` and not `packed` repr, this type may be bigger than `N` bytes /// (which should be OK since C++ just needs to read/write those `N` bytes reliably). +/// +/// +/// For float/double inference, see: +/// * https://github.com/godotengine/godot-proposals/issues/892 +/// * https://github.com/godotengine/godot-cpp/pull/728 +/// +/// We have to do a `target_pointer_width` check *after* code generation, see https://github.com/rust-lang/rust/issues/42587. #[cfg_attr(target_pointer_width = "32", repr(C, align(4)))] #[cfg_attr(target_pointer_width = "64", repr(C, align(8)))] #[derive(Copy, Clone)]