From b15f9deffbf1ef54a867ea0786b4f86e1fa03fa9 Mon Sep 17 00:00:00 2001 From: David Snopek Date: Sat, 6 Dec 2025 21:01:30 -0600 Subject: [PATCH] Use structed data for "deprecated" in `gdextension_interface.json` --- core/extension/gdextension_interface.json | 128 ++++++++++++++---- .../gdextension_interface.schema.json | 34 ++++- ...gdextension_interface_header_generator.cpp | 31 +++-- .../gdextension_interface_header_generator.h | 3 +- core/extension/make_interface_header.py | 62 +++++++-- 5 files changed, 207 insertions(+), 51 deletions(-) diff --git a/core/extension/gdextension_interface.json b/core/extension/gdextension_interface.json index 8639b30ddea..7352d0453ee 100644 --- a/core/extension/gdextension_interface.json +++ b/core/extension/gdextension_interface.json @@ -1116,7 +1116,10 @@ "type": "int32_t" } ], - "deprecated": "Deprecated. Use GDExtensionClassNotification2 instead." + "deprecated": { + "since": "4.2", + "replace_with": "GDExtensionClassNotification2" + } }, { "name": "GDExtensionClassNotification2", @@ -1461,7 +1464,10 @@ ] } ], - "deprecated": "Deprecated. Use GDExtensionClassCreationInfo4 instead." + "deprecated": { + "since": "4.2", + "replace_with": "GDExtensionClassCreationInfo4" + } }, { "name": "GDExtensionClassCreationInfo2", @@ -1579,7 +1585,10 @@ ] } ], - "deprecated": "Deprecated. Use GDExtensionClassCreationInfo4 instead." + "deprecated": { + "since": "4.3", + "replace_with": "GDExtensionClassCreationInfo4" + } }, { "name": "GDExtensionClassCreationInfo3", @@ -1701,7 +1710,10 @@ ] } ], - "deprecated": "Deprecated. Use GDExtensionClassCreationInfo4 instead." + "deprecated": { + "since": "4.4", + "replace_with": "GDExtensionClassCreationInfo4" + } }, { "name": "GDExtensionClassCreationInfo4", @@ -2344,7 +2356,10 @@ "", "`free_func` is necessary if `callable_userdata` needs to be cleaned up when the callable is freed." ], - "deprecated": "Deprecated. Use GDExtensionCallableCustomInfo2 instead." + "deprecated": { + "since": "4.3", + "replace_with": "GDExtensionCallableCustomInfo2" + } }, { "name": "GDExtensionCallableCustomInfo2", @@ -2493,7 +2508,10 @@ "type": "const GDExtensionPropertyInfo*" } ], - "deprecated": "Deprecated. Use GDExtensionScriptInstanceFreePropertyList2 instead." + "deprecated": { + "since": "4.3", + "replace_with": "GDExtensionScriptInstanceFreePropertyList2" + } }, { "name": "GDExtensionScriptInstanceFreePropertyList2", @@ -2697,7 +2715,10 @@ "type": "const GDExtensionMethodInfo*" } ], - "deprecated": "Deprecated. Use GDExtensionScriptInstanceFreeMethodList2 instead." + "deprecated": { + "since": "4.3", + "replace_with": "GDExtensionScriptInstanceFreeMethodList2" + } }, { "name": "GDExtensionScriptInstanceFreeMethodList2", @@ -2807,7 +2828,10 @@ "type": "int32_t" } ], - "deprecated": "Deprecated. Use GDExtensionScriptInstanceNotification2 instead." + "deprecated": { + "since": "4.2", + "replace_with": "GDExtensionScriptInstanceNotification2" + } }, { "name": "GDExtensionScriptInstanceNotification2", @@ -3037,7 +3061,10 @@ "type": "GDExtensionScriptInstanceFree" } ], - "deprecated": "Deprecated. Use GDExtensionScriptInstanceInfo3 instead." + "deprecated": { + "since": "4.2", + "replace_with": "GDExtensionScriptInstanceInfo3" + } }, { "name": "GDExtensionScriptInstanceInfo2", @@ -3147,7 +3174,10 @@ "type": "GDExtensionScriptInstanceFree" } ], - "deprecated": "Deprecated. Use GDExtensionScriptInstanceInfo3 instead." + "deprecated": { + "since": "4.3", + "replace_with": "GDExtensionScriptInstanceInfo3" + } }, { "name": "GDExtensionScriptInstanceInfo3", @@ -3622,7 +3652,10 @@ "Gets the Godot version that the GDExtension was loaded into." ], "since": "4.1", - "deprecated": "Deprecated in Godot 4.5. Use `get_godot_version2` instead." + "deprecated": { + "since": "4.5", + "replace_with": "get_godot_version2" + } }, { "name": "get_godot_version2", @@ -3664,7 +3697,11 @@ "Allocates memory." ], "since": "4.1", - "deprecated": "Deprecated in Godot 4.6. Use `mem_alloc2` instead." + "deprecated": { + "since": "4.6", + "message": "Does not allow explicitly requesting padding.", + "replace_with": "mem_alloc2" + } }, { "name": "mem_realloc", @@ -3694,7 +3731,11 @@ "Reallocates memory." ], "since": "4.1", - "deprecated": "Deprecated in Godot 4.6. Use `mem_realloc2` instead." + "deprecated": { + "since": "4.6", + "message": "Does not allow explicitly requesting padding.", + "replace_with": "mem_realloc2" + } }, { "name": "mem_free", @@ -3714,7 +3755,11 @@ "Frees memory." ], "since": "4.1", - "deprecated": "Deprecated in Godot 4.6. Use `mem_free2` instead." + "deprecated": { + "since": "4.6", + "message": "Does not allow explicitly requesting padding.", + "replace_with": "mem_free2" + } }, { "name": "mem_alloc2", @@ -5885,7 +5930,10 @@ "Creates a String from a UTF-8 encoded C string with the given length." ], "since": "4.1", - "deprecated": "Deprecated in Godot 4.3. Use `string_new_with_utf8_chars_and_len2` instead." + "deprecated": { + "since": "4.3", + "replace_with": "string_new_with_utf8_chars_and_len2" + } }, { "name": "string_new_with_utf8_chars_and_len2", @@ -5955,7 +6003,10 @@ "Creates a String from a UTF-16 encoded C string with the given length." ], "since": "4.1", - "deprecated": "Deprecated in Godot 4.3. Use `string_new_with_utf16_chars_and_len2` instead." + "deprecated": { + "since": "4.3", + "replace_with": "string_new_with_utf16_chars_and_len2" + } }, { "name": "string_new_with_utf16_chars_and_len2", @@ -7512,7 +7563,10 @@ "Sets an Array to be a reference to another Array object." ], "since": "4.1", - "deprecated": "Deprecated in Godot 4.5. use `Array::operator=` instead." + "deprecated": { + "since": "4.5", + "message": "Removed from interface. Use copy constructor instead." + } }, { "name": "array_set_typed", @@ -8214,7 +8268,10 @@ "Creates a script instance that contains the given info and instance data." ], "since": "4.1", - "deprecated": "Deprecated in Godot 4.2. Use `script_instance_create3` instead." + "deprecated": { + "since": "4.2", + "replace_with": "script_instance_create3" + } }, { "name": "script_instance_create2", @@ -8244,7 +8301,10 @@ "Creates a script instance that contains the given info and instance data." ], "since": "4.2", - "deprecated": "Deprecated in Godot 4.3. Use `script_instance_create3` instead." + "deprecated": { + "since": "4.3", + "replace_with": "script_instance_create3" + } }, { "name": "script_instance_create3", @@ -8430,7 +8490,10 @@ "Provided struct can be safely freed once the function returns." ], "since": "4.2", - "deprecated": "Deprecated in Godot 4.3. Use `callable_custom_create2` instead." + "deprecated": { + "since": "4.3", + "replace_with": "callable_custom_create2" + } }, { "name": "callable_custom_create2", @@ -8512,7 +8575,10 @@ "The passed class must be a built-in godot class, or an already-registered extension class. In both cases, object_set_instance() should be called to fully initialize the object." ], "since": "4.1", - "deprecated": "Deprecated in Godot 4.4. Use `classdb_construct_object2` instead." + "deprecated": { + "since": "4.4", + "replace_with": "classdb_construct_object2" + } }, { "name": "classdb_construct_object2", @@ -8637,7 +8703,10 @@ "Provided struct can be safely freed once the function returns." ], "since": "4.1", - "deprecated": "Deprecated in Godot 4.2. Use `classdb_register_extension_class4` instead." + "deprecated": { + "since": "4.2", + "replace_with": "classdb_register_extension_class5" + } }, { "name": "classdb_register_extension_class2", @@ -8679,7 +8748,10 @@ "Provided struct can be safely freed once the function returns." ], "since": "4.2", - "deprecated": "Deprecated in Godot 4.3. Use `classdb_register_extension_class4` instead." + "deprecated": { + "since": "4.3", + "replace_with": "classdb_register_extension_class5" + } }, { "name": "classdb_register_extension_class3", @@ -8721,7 +8793,10 @@ "Provided struct can be safely freed once the function returns." ], "since": "4.3", - "deprecated": "Deprecated in Godot 4.4. Use `classdb_register_extension_class4` instead." + "deprecated": { + "since": "4.4", + "replace_with": "classdb_register_extension_class5" + } }, { "name": "classdb_register_extension_class4", @@ -8763,7 +8838,10 @@ "Provided struct can be safely freed once the function returns." ], "since": "4.4", - "deprecated": "Deprecated in Godot 4.5. Use `classdb_register_extension_class5` instead." + "deprecated": { + "since": "4.5", + "replace_with": "classdb_register_extension_class5" + } }, { "name": "classdb_register_extension_class5", diff --git a/core/extension/gdextension_interface.schema.json b/core/extension/gdextension_interface.schema.json index e3b764d0a10..1f01fab0476 100644 --- a/core/extension/gdextension_interface.schema.json +++ b/core/extension/gdextension_interface.schema.json @@ -37,7 +37,22 @@ } }, "deprecated": { - "type": "string" + "type": "object", + "properties": { + "since": { + "type": "string", + "pattern": "4\\.[0-9]+" + }, + "message": { + "type": "string", + "pattern": "\\.$" + }, + "replace_with": { + "type": "string" + } + }, + "required": ["since"], + "additionalProperties": false } }, "required": [ @@ -256,7 +271,22 @@ "pattern": "4\\.[0-9]+" }, "deprecated": { - "type": "string" + "type": "object", + "properties": { + "since": { + "type": "string", + "pattern": "4\\.[0-9]+" + }, + "message": { + "type": "string", + "pattern": "\\.$" + }, + "replace_with": { + "type": "string" + } + }, + "required": ["since"], + "additionalProperties": false }, "see": { "type": "array", diff --git a/core/extension/gdextension_interface_header_generator.cpp b/core/extension/gdextension_interface_header_generator.cpp index b409c33002d..2f6d1c9f410 100644 --- a/core/extension/gdextension_interface_header_generator.cpp +++ b/core/extension/gdextension_interface_header_generator.cpp @@ -145,7 +145,7 @@ void GDExtensionInterfaceHeaderGenerator::write_doc(const Ref &p_fa, void GDExtensionInterfaceHeaderGenerator::write_simple_type(const Ref &p_fa, const Dictionary &p_type) { String type_and_name = format_type_and_name(p_type["type"], p_type["name"]); - p_fa->store_string(vformat("typedef %s;%s\n", type_and_name, make_deprecated_note(p_type))); + p_fa->store_string(vformat("typedef %s;%s\n", type_and_name, make_deprecated_comment_for_type(p_type))); } void GDExtensionInterfaceHeaderGenerator::write_enum_type(const Ref &p_fa, const Dictionary &p_enum) { @@ -157,14 +157,14 @@ void GDExtensionInterfaceHeaderGenerator::write_enum_type(const Ref } p_fa->store_string(vformat("\t%s = %s,\n", value_dict["name"], (int)value_dict["value"])); } - p_fa->store_string(vformat("} %s;%s\n\n", p_enum["name"], make_deprecated_note(p_enum))); + p_fa->store_string(vformat("} %s;%s\n\n", p_enum["name"], make_deprecated_comment_for_type(p_enum))); } void GDExtensionInterfaceHeaderGenerator::write_function_type(const Ref &p_fa, const Dictionary &p_func) { String args_text = p_func.has("arguments") ? make_args_text(p_func["arguments"]) : ""; String name_and_args = vformat("(*%s)(%s)", p_func["name"], args_text); Dictionary ret = p_func["return_value"]; - p_fa->store_string(vformat("typedef %s;%s\n", format_type_and_name(ret["type"], name_and_args), make_deprecated_note(p_func))); + p_fa->store_string(vformat("typedef %s;%s\n", format_type_and_name(ret["type"], name_and_args), make_deprecated_comment_for_type(p_func))); } void GDExtensionInterfaceHeaderGenerator::write_struct_type(const Ref &p_fa, const Dictionary &p_struct) { @@ -176,7 +176,7 @@ void GDExtensionInterfaceHeaderGenerator::write_struct_type(const Refstore_string(vformat("\t%s;\n", format_type_and_name(member_dict["type"], member_dict["name"]))); } - p_fa->store_string(vformat("} %s;%s\n\n", p_struct["name"], make_deprecated_note(p_struct))); + p_fa->store_string(vformat("} %s;%s\n\n", p_struct["name"], make_deprecated_comment_for_type(p_struct))); } String GDExtensionInterfaceHeaderGenerator::format_type_and_name(const String &p_type, const String &p_name) { @@ -196,11 +196,23 @@ String GDExtensionInterfaceHeaderGenerator::format_type_and_name(const String &p return ret; } -String GDExtensionInterfaceHeaderGenerator::make_deprecated_note(const Dictionary &p_type) { +String GDExtensionInterfaceHeaderGenerator::make_deprecated_message(const Dictionary &p_data) { + PackedStringArray parts; + parts.push_back(vformat("Deprecated in Godot %s.", p_data["since"])); + if (p_data.has("message")) { + parts.push_back(p_data["message"]); + } + if (p_data.has("replace_with")) { + parts.push_back(vformat("Use `%s` instead.", p_data["replace_with"])); + } + return String(" ").join(parts); +} + +String GDExtensionInterfaceHeaderGenerator::make_deprecated_comment_for_type(const Dictionary &p_type) { if (!p_type.has("deprecated")) { return ""; } - return vformat(" /* %s */", p_type["deprecated"]); + return vformat(" /* %s */", make_deprecated_message(p_type["deprecated"])); } String GDExtensionInterfaceHeaderGenerator::make_args_text(const Array &p_args) { @@ -218,12 +230,7 @@ void GDExtensionInterfaceHeaderGenerator::write_interface(const Ref doc.push_back(String("@since ") + (String)p_interface["since"]); if (p_interface.has("deprecated")) { - String deprecated = p_interface["deprecated"]; - if (deprecated.to_lower().begins_with("deprecated")) { - Vector parts = deprecated.split_spaces(1); - deprecated = parts[1]; - } - doc.push_back(String("@deprecated ") + deprecated); + doc.push_back(String("@deprecated ") + make_deprecated_message(p_interface["deprecated"])); } Array orig_doc = p_interface["description"]; diff --git a/core/extension/gdextension_interface_header_generator.h b/core/extension/gdextension_interface_header_generator.h index ebc07e64618..3132bd11495 100644 --- a/core/extension/gdextension_interface_header_generator.h +++ b/core/extension/gdextension_interface_header_generator.h @@ -46,7 +46,8 @@ private: static void write_struct_type(const Ref &p_fa, const Dictionary &p_struct); static String format_type_and_name(const String &p_type, const String &p_name); - static String make_deprecated_note(const Dictionary &p_type); + static String make_deprecated_message(const Dictionary &p_data); + static String make_deprecated_comment_for_type(const Dictionary &p_type); static String make_args_text(const Array &p_args); static void write_interface(const Ref &p_fa, const Dictionary &p_interface); diff --git a/core/extension/make_interface_header.py b/core/extension/make_interface_header.py index 496dfc6e513..e7ba213e0a9 100644 --- a/core/extension/make_interface_header.py +++ b/core/extension/make_interface_header.py @@ -54,11 +54,17 @@ extern "C" { """) handles = [] + type_replacements = [] for type in data["types"]: kind = type["kind"] check_type(kind, type, valid_data_types) - valid_data_types[type["name"]] = True + valid_data_types[type["name"]] = type + + if "deprecated" in type: + check_allowed_keys(type["deprecated"], ["since"], ["message", "replace_with"]) + if "replace_with" in type["deprecated"]: + type_replacements.append((type["name"], type["deprecated"]["replace_with"])) if "description" in type: write_doc(file, type["description"]) @@ -86,6 +92,17 @@ extern "C" { else: raise Exception(f"Unknown kind of type: {kind}") + for type_name, replace_with in type_replacements: + if replace_with not in valid_data_types: + raise Exception(f"Unknown type '{replace_with}' used as replacement for '{type_name}'") + replacement = valid_data_types[replace_with] + if isinstance(replacement, dict) and "deprecated" in replacement: + raise Exception( + f"Cannot use '{replace_with}' as replacement for '{type_name}' because it's deprecated too" + ) + + interface_replacements = [] + valid_interfaces = {} for interface in data["interface"]: check_type("function", interface, valid_data_types) check_allowed_keys( @@ -93,8 +110,24 @@ extern "C" { ["name", "return_value", "arguments", "since", "description"], ["see", "legacy_type_name", "deprecated"], ) + valid_interfaces[interface["name"]] = interface + if "deprecated" in interface: + check_allowed_keys(interface["deprecated"], ["since"], ["message", "replace_with"]) + if "replace_with" in interface["deprecated"]: + interface_replacements.append((interface["name"], interface["deprecated"]["replace_with"])) write_interface(file, interface) + for function_name, replace_with in interface_replacements: + if replace_with not in valid_interfaces: + raise Exception( + f"Unknown interface function '{replace_with}' used as replacement for '{function_name}'" + ) + replacement = valid_interfaces[replace_with] + if "deprecated" in replacement: + raise Exception( + f"Cannot use '{replace_with}' as replacement for '{function_name}' because it's deprecated too" + ) + file.write("""\ #ifdef __cplusplus } @@ -202,14 +235,24 @@ def write_doc(file, doc, indent=""): file.write(indent + " */\n") -def make_deprecated_note(type): +def make_deprecated_message(data): + parts = [ + f"Deprecated in Godot {data['since']}.", + data["message"] if "message" in data else "", + f"Use `{data['replace_with']}` instead." if "replace_with" in data else "", + ] + return " ".join([x for x in parts if x.strip() != ""]) + + +def make_deprecated_comment_for_type(type): if "deprecated" not in type: return "" - return f" /* {type['deprecated']} */" + message = make_deprecated_message(type["deprecated"]) + return f" /* {message} */" def write_simple_type(file, type): - file.write(f"typedef {format_type_and_name(type['type'], type['name'])};{make_deprecated_note(type)}\n") + file.write(f"typedef {format_type_and_name(type['type'], type['name'])};{make_deprecated_comment_for_type(type)}\n") def write_enum_type(file, enum): @@ -219,7 +262,7 @@ def write_enum_type(file, enum): if "description" in value: write_doc(file, value["description"], "\t") file.write(f"\t{value['name']} = {value['value']},\n") - file.write(f"}} {enum['name']};{make_deprecated_note(enum)}\n\n") + file.write(f"}} {enum['name']};{make_deprecated_comment_for_type(enum)}\n\n") def make_args_text(args): @@ -234,7 +277,7 @@ def write_function_type(file, fn): args_text = make_args_text(fn["arguments"]) if ("arguments" in fn) else "" name_and_args = f"(*{fn['name']})({args_text})" file.write( - f"typedef {format_type_and_name(fn['return_value']['type'], name_and_args)};{make_deprecated_note(fn)}\n" + f"typedef {format_type_and_name(fn['return_value']['type'], name_and_args)};{make_deprecated_comment_for_type(fn)}\n" ) @@ -245,7 +288,7 @@ def write_struct_type(file, struct): if "description" in member: write_doc(file, member["description"], "\t") file.write(f"\t{format_type_and_name(member['type'], member['name'])};\n") - file.write(f"}} {struct['name']};{make_deprecated_note(struct)}\n\n") + file.write(f"}} {struct['name']};{make_deprecated_comment_for_type(struct)}\n\n") def write_interface(file, interface): @@ -255,10 +298,7 @@ def write_interface(file, interface): ] if "deprecated" in interface: - if interface["deprecated"].lower().startswith("deprecated "): - parts = interface["deprecated"].split(" ", 1) - interface["deprecated"] = parts[1] - doc.append(f"@deprecated {interface['deprecated']}") + doc.append(f"@deprecated {make_deprecated_message(interface['deprecated'])}") doc += [ "",