use std::collections::HashMap;
use proc_macro2::Ident;
use crate::context::Context;
use crate::models::domain::{
BuildConfiguration, BuiltinClass, BuiltinMethod, BuiltinSize, BuiltinVariant, Class,
ClassCommons, ClassConstant, ClassConstantValue, ClassMethod, ClassSignal, Constructor, Enum,
EnumReplacements, Enumerator, EnumeratorValue, ExtensionApi, FlowDirection, FnDirection,
FnParam, FnQualifier, FnReturn, FunctionCommon, GodotApiVersion, ModName, NativeStructure,
Operator, RustTy, Singleton, TyName, UtilityFunction,
};
use crate::models::json::{
JsonBuiltinClass, JsonBuiltinMethod, JsonBuiltinSizes, JsonClass, JsonClassConstant,
JsonClassMethod, JsonConstructor, JsonEnum, JsonEnumConstant, JsonExtensionApi, JsonHeader,
JsonMethodArg, JsonMethodReturn, JsonNativeStructure, JsonOperator, JsonSignal, JsonSingleton,
JsonUtilityFunction,
};
use crate::util::{get_api_level, ident, option_as_slice};
use crate::{conv, special_cases};
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<BuiltinSize> {
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
}
}
impl Class {
pub fn from_json(json: &JsonClass, ctx: &mut Context) -> Option<Self> {
let ty_name = TyName::from_godot(&json.name);
if special_cases::is_class_deleted(&ty_name) {
return None;
}
let is_experimental = special_cases::is_class_experimental(&ty_name.godot_ty);
let is_instantiable = special_cases::is_class_instantiable(&ty_name) .unwrap_or(json.is_instantiable);
let is_final = ctx.is_final(&ty_name);
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();
let signals = option_as_slice(&json.signals)
.iter()
.filter_map(|s| {
let surrounding_class = &ty_name;
ClassSignal::from_json(s, surrounding_class, ctx)
})
.collect();
let base_class = json
.inherits
.as_ref()
.map(|godot_name| TyName::from_godot(godot_name));
Some(Self {
common: ClassCommons {
name: ty_name,
mod_name,
},
is_refcounted: json.is_refcounted,
is_instantiable,
is_experimental,
is_final,
base_class,
api_level: get_api_level(json),
constants,
enums,
methods,
signals,
})
}
}
impl BuiltinClass {
pub fn from_json(json: &JsonBuiltinClass, ctx: &mut Context) -> Option<Self> {
let ty_name = TyName::from_godot_builtin(json);
if special_cases::is_builtin_type_deleted(&ty_name) {
return None;
}
let mod_name = ModName::from_godot_builtin(json);
let inner_name = TyName::from_godot(&format!("Inner{}", 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| {
BuiltinMethod::from_json(m, &ty_name, &inner_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),
}
}
}
impl BuiltinVariant {
pub fn all_from_json(
global_enums: &[JsonEnum],
builtin_classes: &[JsonBuiltinClass],
ctx: &mut Context,
) -> Vec<Self> {
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");
let builtin_classes: HashMap<String, &JsonBuiltinClass> = 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::<Vec<_>>();
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;
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(), godot_snake_name: conv::to_snake_case(json_variant_enumerator_name),
builtin_class,
variant_type_ord: json_variant_enumerator_ord,
}
}
}
impl Constructor {
pub fn from_json(json: &JsonConstructor) -> Self {
Self {
index: json.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(),
}
}
}
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,
}
}
}
impl BuiltinMethod {
pub fn from_json(
method: &JsonBuiltinMethod,
builtin_name: &TyName,
inner_class_name: &TyName,
ctx: &mut Context,
) -> Option<Self> {
if special_cases::is_builtin_method_deleted(builtin_name, method) {
return None;
}
let is_exposed_in_outer =
special_cases::is_builtin_method_exposed(builtin_name, &method.name);
let return_value = {
let return_value = &method
.return_type
.as_deref()
.map(JsonMethodReturn::from_type_no_meta);
let flow = if !is_exposed_in_outer
&& matches!(builtin_name.godot_ty.as_str(), "Array" | "Dictionary")
&& matches!(method.return_type.as_deref(), Some("Array" | "Dictionary"))
{
FlowDirection::RustToGodot } else {
FlowDirection::GodotToRust
};
FnReturn::new(return_value, flow, ctx)
};
let parameters =
FnParam::builder().build_many(&method.arguments, FlowDirection::RustToGodot, ctx);
let surrounding_class = {
let rust_ty = if is_exposed_in_outer {
match conv::to_rust_type(&builtin_name.godot_ty, None, None, ctx) {
RustTy::BuiltinIdent { ty, .. } => ty,
_ => panic!("Builtin type should map to BuiltinIdent"),
}
} else {
inner_class_name.rust_ty.clone()
};
TyName {
godot_ty: builtin_name.godot_ty.clone(),
rust_ty,
}
};
Some(Self {
common: FunctionCommon {
name: method.name.clone(),
godot_name: method.name.clone(),
parameters,
return_value,
is_vararg: method.is_vararg,
is_private: false, is_virtual_required: false,
is_unsafe: false, direction: FnDirection::Outbound {
hash: method.hash.expect("hash absent for builtin method"),
},
deprecation_msg: None, },
qualifier: FnQualifier::from_const_static(method.is_const, method.is_static),
surrounding_class,
is_exposed_in_outer,
})
}
}
impl ClassMethod {
pub fn from_json(
method: &JsonClassMethod,
class_name: &TyName,
ctx: &mut Context,
) -> Option<ClassMethod> {
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<Self> {
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.as_ref(),
class_name,
FnDirection::Outbound { hash },
ctx,
)
}
fn from_json_virtual(
method: &JsonClassMethod,
class_name: &TyName,
ctx: &mut Context,
) -> Option<Self> {
assert!(method.is_virtual);
let direction = FnDirection::Virtual {
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
hash: {
let hash_i64 = method.hash.unwrap_or_else(|| {
panic!(
"virtual class methods must have a hash since Godot 4.4; missing: {}.{}",
class_name.godot_ty, method.name
)
});
hash_i64.try_into().unwrap_or_else(|_| {
panic!(
"virtual method {}.{} has hash {} that is out of range for u32",
class_name.godot_ty, method.name, hash_i64
)
})
},
};
let rust_method_name = Self::make_virtual_method_name(class_name, &method.name);
Self::from_json_inner(method, rust_method_name, class_name, direction, ctx)
}
fn from_json_inner(
method: &JsonClassMethod,
rust_method_name: &str,
class_name: &TyName,
direction: FnDirection,
ctx: &mut Context,
) -> Option<ClassMethod> {
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 = {
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)
};
#[cfg(before_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.4")))]
let is_virtual_required =
special_cases::is_virtual_method_required(&class_name, &method.name);
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
#[allow(clippy::let_and_return)]
let is_virtual_required = method.is_virtual && {
let is_required_in_json = method.is_required.unwrap_or_else(|| {
panic!(
"virtual method {}::{} lacks field `is_required`",
class_name.rust_ty, rust_method_name
);
});
is_required_in_json
};
let enum_replacements = validate_enum_replacements(
class_name,
&method.name,
option_as_slice(&method.arguments),
method.return_value.is_some(),
);
let (param_flow, return_flow) = match &direction {
FnDirection::Outbound { .. } => {
(FlowDirection::RustToGodot, FlowDirection::GodotToRust)
}
FnDirection::Virtual { .. } => (FlowDirection::GodotToRust, FlowDirection::RustToGodot),
};
let parameters = FnParam::builder()
.enum_replacements(enum_replacements)
.build_many(&method.arguments, param_flow, ctx);
let return_value = FnReturn::with_enum_replacements(
&method.return_value,
enum_replacements,
return_flow,
ctx,
);
let is_unsafe = Self::function_uses_pointers(¶meters, &return_value);
let rust_method_name = if is_unsafe && method.is_virtual {
conv::make_unsafe_virtual_fn_name(rust_method_name)
} else {
rust_method_name.to_string()
};
let deprecation_msg = special_cases::get_class_method_deprecation(class_name, method);
Some(Self {
common: FunctionCommon {
name: rust_method_name,
godot_name: godot_method_name,
parameters,
return_value,
is_vararg: method.is_vararg,
is_private,
is_virtual_required,
is_unsafe,
direction,
deprecation_msg,
},
qualifier,
surrounding_class: class_name.clone(),
})
}
fn make_virtual_method_name<'m>(class_name: &TyName, godot_method_name: &'m str) -> &'m str {
if let Some(rust_name) =
special_cases::maybe_rename_virtual_method(class_name, godot_method_name)
{
return rust_name;
}
godot_method_name
.strip_prefix('_')
.unwrap_or(godot_method_name)
}
fn function_uses_pointers(parameters: &[FnParam], return_value: &FnReturn) -> bool {
let has_pointer_params = parameters
.iter()
.any(|param| matches!(param.type_, RustTy::RawPointer { .. }));
let has_pointer_return = matches!(return_value.type_, Some(RustTy::RawPointer { .. }));
has_pointer_params || has_pointer_return
}
}
impl ClassSignal {
pub fn from_json(
json_signal: &JsonSignal,
surrounding_class: &TyName,
ctx: &mut Context,
) -> Option<Self> {
if special_cases::is_signal_deleted(surrounding_class, json_signal) {
return None;
}
let flow = FlowDirection::RustToGodot;
Some(Self {
name: json_signal.name.clone(),
parameters: FnParam::builder().build_many(&json_signal.arguments, flow, ctx),
surrounding_class: surrounding_class.clone(),
})
}
}
impl UtilityFunction {
pub fn from_json(function: &JsonUtilityFunction, ctx: &mut Context) -> Option<Self> {
if special_cases::is_utility_function_deleted(function, ctx) {
return None;
}
let is_private = special_cases::is_utility_function_private(function);
let args = option_as_slice(&function.arguments);
let parameters = if function.is_vararg && args.len() == 1 && args[0].name == "arg1" {
vec![]
} else {
FnParam::builder().build_many(&function.arguments, FlowDirection::RustToGodot, ctx)
};
let json_return = function
.return_type
.as_deref()
.map(JsonMethodReturn::from_type_no_meta);
let return_value = FnReturn::new(&json_return, FlowDirection::GodotToRust, ctx);
let godot_method_name = function.name.clone();
let rust_method_name = godot_method_name.clone();
Some(Self {
common: FunctionCommon {
name: rust_method_name,
godot_name: godot_method_name,
parameters,
return_value,
is_vararg: function.is_vararg,
is_private,
is_virtual_required: false,
is_unsafe: false, direction: FnDirection::Outbound {
hash: function.hash,
},
deprecation_msg: None, },
})
}
}
impl Enum {
pub fn from_json(json_enum: &JsonEnum, surrounding_class: Option<&TyName>) -> Self {
let godot_name = &json_enum.name;
let is_bitfield = special_cases::is_enum_bitfield(surrounding_class, godot_name)
.unwrap_or(json_enum.is_bitfield);
let is_private = special_cases::is_enum_private(surrounding_class, godot_name);
let is_exhaustive = special_cases::is_enum_exhaustive(surrounding_class, godot_name);
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| {
if e.name == "OP_MODULE" {
"OP_MODULO"
} else {
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: Vec<Enumerator> = json_enum
.values
.iter()
.zip(rust_enumerator_names)
.map(|(json_constant, rust_name)| {
Enumerator::from_json(json_constant, rust_name, is_bitfield)
})
.collect();
let max_index = Enum::find_index_enum_max_impl(is_bitfield, &enumerators);
Self {
name: ident(&rust_enum_name),
godot_name: godot_name.clone(),
surrounding_class: surrounding_class.cloned(),
is_bitfield,
is_private,
is_exhaustive,
enumerators,
max_index,
}
}
}
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,
}
}
}
impl ClassConstant {
pub fn from_json(json: &JsonClassConstant) -> Self {
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,
}
}
}
fn validate_enum_replacements(
class_ty: &TyName,
godot_method_name: &str,
method_arguments: &[JsonMethodArg],
has_return_type: bool,
) -> EnumReplacements {
let replacements =
special_cases::get_class_method_param_enum_replacement(class_ty, godot_method_name);
for (param_name, enum_name, _) in replacements {
if param_name.is_empty() {
assert!(
has_return_type,
"Method `{class}.{godot_method_name}` has no return type, but replacement with `{enum_name}` is declared",
class = class_ty.godot_ty
);
} else if !method_arguments.iter().any(|arg| arg.name == *param_name) {
let available_params = method_arguments
.iter()
.map(|arg| format!(" * {}: {}", arg.name, arg.type_))
.collect::<Vec<_>>()
.join("\n");
panic!(
"Method `{class}.{godot_method_name}` has no parameter `{param_name}`, but a replacement with `{enum_name}` is declared\n\
\n{count} parameters available:\n{available_params}\n",
class = class_ty.godot_ty,
count = method_arguments.len(),
);
}
}
replacements
}
impl NativeStructure {
pub fn from_json(json: &JsonNativeStructure) -> Self {
let format = special_cases::get_native_struct_definition(&json.name)
.map(|s| s.to_string())
.unwrap_or_else(|| json.format.clone());
Self {
name: json.name.clone(),
format,
}
}
}