From 412d1f942d929631ddff26e1e6a64b983e8b51e0 Mon Sep 17 00:00:00 2001 From: decryptedchaos Date: Thu, 8 Jan 2026 21:03:09 -0600 Subject: [PATCH 01/19] Baseline framework for GDScript Structs --- core/extension/extension_api_dump.cpp | 5 + core/extension/gdextension_interface.cpp | 10 + core/io/json.cpp | 10 + core/variant/variant.cpp | 27 +- core/variant/variant.h | 4 + core/variant/variant_call.cpp | 117 ++++++++ core/variant/variant_construct.cpp | 43 +++ core/variant/variant_internal.h | 28 ++ core/variant/variant_setget.cpp | 20 ++ core/variant/variant_utility.cpp | 36 +-- modules/gdscript/editor/gdscript_docgen.cpp | 8 + modules/gdscript/gdscript.cpp | 48 ++++ modules/gdscript/gdscript.h | 25 ++ modules/gdscript/gdscript_analyzer.cpp | 72 +++++ modules/gdscript/gdscript_analyzer.h | 2 + modules/gdscript/gdscript_byte_codegen.cpp | 5 + modules/gdscript/gdscript_compiler.cpp | 91 +++++- modules/gdscript/gdscript_compiler.h | 1 + modules/gdscript/gdscript_editor.cpp | 12 + modules/gdscript/gdscript_function.h | 12 + modules/gdscript/gdscript_parser.cpp | 157 +++++++++++ modules/gdscript/gdscript_parser.h | 82 ++++++ modules/gdscript/gdscript_struct.cpp | 260 ++++++++++++++++++ modules/gdscript/gdscript_struct.h | 169 ++++++++++++ modules/gdscript/gdscript_tokenizer.cpp | 2 + modules/gdscript/gdscript_tokenizer.h | 1 + .../gdscript_extend_parser.cpp | 6 + 27 files changed, 1221 insertions(+), 32 deletions(-) create mode 100644 modules/gdscript/gdscript_struct.cpp create mode 100644 modules/gdscript/gdscript_struct.h diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp index d2763771291..d18489c003e 100644 --- a/core/extension/extension_api_dump.cpp +++ b/core/extension/extension_api_dump.cpp @@ -40,6 +40,9 @@ #include "core/templates/pair.h" #include "core/version.h" +// Forward declaration for struct type +class GDScriptStructInstance; + #ifdef TOOLS_ENABLED #include "editor/doc/editor_help.h" @@ -210,6 +213,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { { Variant::NODE_PATH, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 }, { Variant::RID, sizeof(uint64_t), sizeof(uint64_t), sizeof(uint64_t), sizeof(uint64_t) }, { Variant::OBJECT, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 }, + { Variant::STRUCT, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 }, { Variant::CALLABLE, sizeof(Callable), sizeof(Callable), sizeof(Callable), sizeof(Callable) }, // Hardcoded align. { Variant::SIGNAL, sizeof(Signal), sizeof(Signal), sizeof(Signal), sizeof(Signal) }, // Hardcoded align. { Variant::DICTIONARY, ptrsize_32, ptrsize_64, ptrsize_32, ptrsize_64 }, @@ -252,6 +256,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { static_assert(type_size_array[Variant::NODE_PATH][sizeof(void *)] == sizeof(NodePath), "Size of NodePath mismatch"); static_assert(type_size_array[Variant::RID][sizeof(void *)] == sizeof(RID), "Size of RID mismatch"); static_assert(type_size_array[Variant::OBJECT][sizeof(void *)] == sizeof(Object *), "Size of Object mismatch"); + static_assert(type_size_array[Variant::STRUCT][sizeof(void *)] == sizeof(GDScriptStructInstance *), "Size of Struct mismatch"); static_assert(type_size_array[Variant::CALLABLE][sizeof(void *)] == sizeof(Callable), "Size of Callable mismatch"); static_assert(type_size_array[Variant::SIGNAL][sizeof(void *)] == sizeof(Signal), "Size of Signal mismatch"); static_assert(type_size_array[Variant::DICTIONARY][sizeof(void *)] == sizeof(Dictionary), "Size of Dictionary mismatch"); diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index 988446e4cdf..3f8c176e79d 100644 --- a/core/extension/gdextension_interface.cpp +++ b/core/extension/gdextension_interface.cpp @@ -817,6 +817,11 @@ static GDExtensionPtrOperatorEvaluator gdextension_variant_get_ptr_operator_eval return (GDExtensionPtrOperatorEvaluator)Variant::get_ptr_operator_evaluator(Variant::Operator(p_operator), Variant::Type(p_type_a), Variant::Type(p_type_b)); } static GDExtensionPtrBuiltInMethod gdextension_variant_get_ptr_builtin_method(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_method, GDExtensionInt p_hash) { + // STRUCT type doesn't support built-in methods + if (p_type == (int)Variant::STRUCT) { + return nullptr; + } + const StringName method = *reinterpret_cast(p_method); uint32_t hash = Variant::get_builtin_method_hash(Variant::Type(p_type), method); if (hash != p_hash) { @@ -827,6 +832,11 @@ static GDExtensionPtrBuiltInMethod gdextension_variant_get_ptr_builtin_method(GD return (GDExtensionPtrBuiltInMethod)Variant::get_ptr_builtin_method(Variant::Type(p_type), method); } static GDExtensionPtrConstructor gdextension_variant_get_ptr_constructor(GDExtensionVariantType p_type, int32_t p_constructor) { + // STRUCT type doesn't support constructors (yet) + if (p_type == (int)Variant::STRUCT) { + return nullptr; + } + return (GDExtensionPtrConstructor)Variant::get_ptr_constructor(Variant::Type(p_type), p_constructor); } static GDExtensionPtrDestructor gdextension_variant_get_ptr_destructor(GDExtensionVariantType p_type) { diff --git a/core/io/json.cpp b/core/io/json.cpp index 96ecc9f8a7b..48f5cca85cb 100644 --- a/core/io/json.cpp +++ b/core/io/json.cpp @@ -824,6 +824,11 @@ Variant JSON::_from_native(const Variant &p_variant, bool p_full_objects, int p_ return ret; } break; + case Variant::STRUCT: { + // TODO: Implement struct serialization + ERR_FAIL_V_MSG(Variant(), vformat("Struct serialization not yet implemented.")); + } break; + case Variant::DICTIONARY: { const Dictionary dict = p_variant; @@ -1297,6 +1302,11 @@ Variant JSON::_to_native(const Variant &p_json, bool p_allow_objects, int p_dept // Nothing to do at this stage. `Object` should be treated as a class, not as a built-in type. } break; + case Variant::STRUCT: { + // TODO: Implement struct deserialization + ERR_FAIL_V_MSG(Variant(), vformat("Struct deserialization not yet implemented.")); + } break; + case Variant::DICTIONARY: { LOAD_ARGS_CHECK_FACTOR(2); diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index 5504104074e..14e97d5ab00 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -37,6 +37,7 @@ #include "core/io/resource.h" #include "core/math/math_funcs.h" #include "core/variant/variant_parser.h" +#include "modules/gdscript/gdscript_struct.h" PagedAllocator Variant::Pools::_bucket_small; PagedAllocator Variant::Pools::_bucket_medium; @@ -119,6 +120,9 @@ String Variant::get_type_name(Variant::Type p_type) { case OBJECT: { return "Object"; } + case STRUCT: { + return "Struct"; + } case CALLABLE: { return "Callable"; } @@ -1311,6 +1315,15 @@ void Variant::reference(const Variant &p_variant) { _data.packed_array = PackedArrayRef::create(); } } break; + case STRUCT: { + // Reference the struct instance + GDScriptStructInstance *struct_instance = const_cast(reinterpret_cast(p_variant._data._mem)); + if (struct_instance) { + struct_instance->reference(); + } + // Copy the pointer bytes + memcpy(_data._mem, p_variant._data._mem, sizeof(_data._mem)); + } break; default: { } } @@ -1480,6 +1493,18 @@ void Variant::_clear_internal() { case PACKED_VECTOR4_ARRAY: { PackedArrayRefBase::destroy(_data.packed_array); } break; + case STRUCT: { + // Unreference the struct instance + GDScriptStructInstance *struct_instance = reinterpret_cast(_data._mem); + if (struct_instance) { + struct_instance->unreference(); + if (struct_instance->get_reference_count() == 0) { + memdelete(struct_instance); + } + } + // Clear the pointer + memset(_data._mem, 0, sizeof(_data._mem)); + } break; default: { // Not needed, there is no point. The following do not allocate memory: // VECTOR2, VECTOR3, VECTOR4, RECT2, PLANE, QUATERNION, COLOR. @@ -3490,7 +3515,7 @@ void Variant::construct_from_string(const String &p_string, Variant &r_value, Ob String Variant::get_construct_string() const { String vars; - VariantWriter::write_to_string(*this, vars, nullptr, nullptr, true, true); + VariantWriter::write_to_string(*this, vars); return vars; } diff --git a/core/variant/variant.h b/core/variant/variant.h index b7e84054e4f..3e0f96a1d98 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -67,6 +67,7 @@ class Object; class RefCounted; +class GDScriptStructInstance; template class Ref; @@ -123,6 +124,7 @@ class Variant { NODE_PATH, RID, OBJECT, + STRUCT, CALLABLE, SIGNAL, DICTIONARY, @@ -175,6 +177,7 @@ class Variant { friend struct _VariantCall; friend class VariantInternal; + friend class GDScriptStructClass; // Needed for proper struct construction // Variant takes 24 bytes when real_t is float, and 40 bytes if double. // It only allocates extra memory for AABB/Transform2D (24, 48 if double), // Basis/Transform3D (48, 96 if double), Projection (64, 128 if double), @@ -310,6 +313,7 @@ class Variant { true, //NODE_PATH, false, //RID, true, //OBJECT, + true, //STRUCT, true, //CALLABLE, true, //SIGNAL, true, //DICTIONARY, diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index 0a7bb7d5988..15fac6558e1 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1376,6 +1376,12 @@ void Variant::callp(const StringName &p_method, const Variant **p_args, int p_ar } else { r_error.error = Callable::CallError::CALL_OK; + // STRUCT type doesn't have built-in methods + if (type == STRUCT) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + return; + } + const VariantBuiltInMethodInfo *imf = builtin_method_info[type].getptr(p_method); if (!imf) { @@ -1408,6 +1414,12 @@ void Variant::call_const(const StringName &p_method, const Variant **p_args, int } else { r_error.error = Callable::CallError::CALL_OK; + // STRUCT type doesn't have built-in methods + if (type == STRUCT) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + return; + } + const VariantBuiltInMethodInfo *imf = builtin_method_info[type].getptr(p_method); if (!imf) { @@ -1427,6 +1439,12 @@ void Variant::call_const(const StringName &p_method, const Variant **p_args, int void Variant::call_static(Variant::Type p_type, const StringName &p_method, const Variant **p_args, int p_argcount, Variant &r_ret, Callable::CallError &r_error) { r_error.error = Callable::CallError::CALL_OK; + // STRUCT type doesn't have built-in methods + if (p_type == STRUCT) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + return; + } + const VariantBuiltInMethodInfo *imf = builtin_method_info[p_type].getptr(p_method); if (!imf) { @@ -1452,16 +1470,33 @@ bool Variant::has_method(const StringName &p_method) const { return obj->has_method(p_method); } + // STRUCT type doesn't have built-in methods + if (type == STRUCT) { + return false; + } + return builtin_method_info[type].has(p_method); } bool Variant::has_builtin_method(Variant::Type p_type, const StringName &p_method) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, false); + + // STRUCT type doesn't have built-in methods + if (p_type == STRUCT) { + return false; + } + return builtin_method_info[p_type].has(p_method); } Variant::ValidatedBuiltInMethod Variant::get_validated_builtin_method(Variant::Type p_type, const StringName &p_method) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, nullptr); + + // STRUCT type doesn't have built-in methods + if (p_type == STRUCT) { + return nullptr; + } + const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].getptr(p_method); ERR_FAIL_NULL_V(method, nullptr); return method->validated_call; @@ -1469,6 +1504,12 @@ Variant::ValidatedBuiltInMethod Variant::get_validated_builtin_method(Variant::T Variant::PTRBuiltInMethod Variant::get_ptr_builtin_method(Variant::Type p_type, const StringName &p_method) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, nullptr); + + // STRUCT type doesn't have built-in methods + if (p_type == STRUCT) { + return nullptr; + } + const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].getptr(p_method); ERR_FAIL_NULL_V(method, nullptr); return method->ptrcall; @@ -1476,6 +1517,12 @@ Variant::PTRBuiltInMethod Variant::get_ptr_builtin_method(Variant::Type p_type, MethodInfo Variant::get_builtin_method_info(Variant::Type p_type, const StringName &p_method) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, MethodInfo()); + + // STRUCT type doesn't have built-in methods + if (p_type == STRUCT) { + return MethodInfo(); + } + const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].getptr(p_method); ERR_FAIL_NULL_V(method, MethodInfo()); return method->get_method_info(p_method); @@ -1483,6 +1530,12 @@ MethodInfo Variant::get_builtin_method_info(Variant::Type p_type, const StringNa int Variant::get_builtin_method_argument_count(Variant::Type p_type, const StringName &p_method) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, 0); + + // STRUCT type doesn't have built-in methods + if (p_type == STRUCT) { + return 0; + } + const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].getptr(p_method); ERR_FAIL_NULL_V(method, 0); return method->argument_count; @@ -1490,6 +1543,12 @@ int Variant::get_builtin_method_argument_count(Variant::Type p_type, const Strin Variant::Type Variant::get_builtin_method_argument_type(Variant::Type p_type, const StringName &p_method, int p_argument) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, Variant::NIL); + + // STRUCT type doesn't have built-in methods + if (p_type == STRUCT) { + return Variant::NIL; + } + const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].getptr(p_method); ERR_FAIL_NULL_V(method, Variant::NIL); ERR_FAIL_INDEX_V(p_argument, method->argument_count, Variant::NIL); @@ -1498,6 +1557,11 @@ Variant::Type Variant::get_builtin_method_argument_type(Variant::Type p_type, co String Variant::get_builtin_method_argument_name(Variant::Type p_type, const StringName &p_method, int p_argument) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, String()); + + // STRUCT type doesn't have built-in methods + if (p_type == STRUCT) { + return String(); + } const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].getptr(p_method); ERR_FAIL_NULL_V(method, String()); #ifdef DEBUG_ENABLED @@ -1510,6 +1574,12 @@ String Variant::get_builtin_method_argument_name(Variant::Type p_type, const Str Vector Variant::get_builtin_method_default_arguments(Variant::Type p_type, const StringName &p_method) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, Vector()); + + // STRUCT type doesn't have built-in methods + if (p_type == STRUCT) { + return Vector(); + } + const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].getptr(p_method); ERR_FAIL_NULL_V(method, Vector()); return method->default_arguments; @@ -1517,6 +1587,12 @@ Vector Variant::get_builtin_method_default_arguments(Variant::Type p_ty bool Variant::has_builtin_method_return_value(Variant::Type p_type, const StringName &p_method) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, false); + + // STRUCT type doesn't have built-in methods + if (p_type == STRUCT) { + return false; + } + const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].getptr(p_method); ERR_FAIL_NULL_V(method, false); return method->has_return_type; @@ -1524,6 +1600,12 @@ bool Variant::has_builtin_method_return_value(Variant::Type p_type, const String void Variant::get_builtin_method_list(Variant::Type p_type, List *p_list) { ERR_FAIL_INDEX(p_type, Variant::VARIANT_MAX); + + // STRUCT type doesn't have built-in methods + if (p_type == STRUCT) { + return; + } + for (const StringName &E : builtin_method_names[p_type]) { p_list->push_back(E); } @@ -1531,11 +1613,23 @@ void Variant::get_builtin_method_list(Variant::Type p_type, List *p_ int Variant::get_builtin_method_count(Variant::Type p_type) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, -1); + + // STRUCT type doesn't have built-in methods + if (p_type == STRUCT) { + return 0; + } + return builtin_method_names[p_type].size(); } Variant::Type Variant::get_builtin_method_return_type(Variant::Type p_type, const StringName &p_method) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, Variant::NIL); + + // STRUCT type doesn't have built-in methods + if (p_type == STRUCT) { + return Variant::NIL; + } + const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].getptr(p_method); ERR_FAIL_NULL_V(method, Variant::NIL); return method->return_type; @@ -1543,6 +1637,12 @@ Variant::Type Variant::get_builtin_method_return_type(Variant::Type p_type, cons bool Variant::is_builtin_method_const(Variant::Type p_type, const StringName &p_method) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, false); + + // STRUCT type doesn't have built-in methods + if (p_type == STRUCT) { + return false; + } + const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].getptr(p_method); ERR_FAIL_NULL_V(method, false); return method->is_const; @@ -1550,6 +1650,12 @@ bool Variant::is_builtin_method_const(Variant::Type p_type, const StringName &p_ bool Variant::is_builtin_method_static(Variant::Type p_type, const StringName &p_method) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, false); + + // STRUCT type doesn't have built-in methods + if (p_type == STRUCT) { + return false; + } + const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].getptr(p_method); ERR_FAIL_NULL_V(method, false); return method->is_static; @@ -1564,6 +1670,12 @@ bool Variant::is_builtin_method_vararg(Variant::Type p_type, const StringName &p uint32_t Variant::get_builtin_method_hash(Variant::Type p_type, const StringName &p_method) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, 0); + + // STRUCT type doesn't have built-in methods + if (p_type == STRUCT) { + return 0; + } + const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].getptr(p_method); ERR_FAIL_NULL_V(method, 0); uint32_t hash = hash_murmur3_one_32(method->is_const); @@ -1588,6 +1700,11 @@ void Variant::get_method_list(List *p_list) const { obj->get_method_list(p_list); } } else { + // STRUCT type doesn't have built-in methods + if (type == STRUCT) { + return; + } + for (const StringName &E : builtin_method_names[type]) { const VariantBuiltInMethodInfo *method = builtin_method_info[type].getptr(E); ERR_CONTINUE(!method); diff --git a/core/variant/variant_construct.cpp b/core/variant/variant_construct.cpp index 5d684c96a23..52130a54852 100644 --- a/core/variant/variant_construct.cpp +++ b/core/variant/variant_construct.cpp @@ -265,6 +265,15 @@ void Variant::_unregister_variant_constructors() { void Variant::construct(Variant::Type p_type, Variant &base, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { ERR_FAIL_INDEX(p_type, Variant::VARIANT_MAX); + + // STRUCT type - special handling since structs are dynamically defined + if (p_type == STRUCT) { + // For now, create a null/empty struct + // Actual struct construction happens through GDScriptStructClass::new() + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + ERR_FAIL_MSG("Cannot construct generic STRUCT type. Use specific struct type's new() method."); + } + uint32_t s = construct_data[p_type].size(); for (uint32_t i = 0; i < s; i++) { int argc = construct_data[p_type][i].argument_count; @@ -297,30 +306,59 @@ int Variant::get_constructor_count(Variant::Type p_type) { Variant::ValidatedConstructor Variant::get_validated_constructor(Variant::Type p_type, int p_constructor) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, nullptr); + + // STRUCT type doesn't have constructors (yet) + if (p_type == STRUCT) { + return nullptr; + } + ERR_FAIL_INDEX_V(p_constructor, (int)construct_data[p_type].size(), nullptr); return construct_data[p_type][p_constructor].validated_construct; } Variant::PTRConstructor Variant::get_ptr_constructor(Variant::Type p_type, int p_constructor) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, nullptr); + + // STRUCT type doesn't have constructors (yet) + if (p_type == STRUCT) { + return nullptr; + } + ERR_FAIL_INDEX_V(p_constructor, (int)construct_data[p_type].size(), nullptr); return construct_data[p_type][p_constructor].ptr_construct; } int Variant::get_constructor_argument_count(Variant::Type p_type, int p_constructor) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, -1); + + // STRUCT type doesn't have constructors (yet) + if (p_type == STRUCT) { + return -1; + } + ERR_FAIL_INDEX_V(p_constructor, (int)construct_data[p_type].size(), -1); return construct_data[p_type][p_constructor].argument_count; } Variant::Type Variant::get_constructor_argument_type(Variant::Type p_type, int p_constructor, int p_argument) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, Variant::VARIANT_MAX); + + // STRUCT type doesn't have constructors (yet) + if (p_type == STRUCT) { + return Variant::NIL; + } + ERR_FAIL_INDEX_V(p_constructor, (int)construct_data[p_type].size(), Variant::VARIANT_MAX); return construct_data[p_type][p_constructor].get_argument_type(p_argument); } String Variant::get_constructor_argument_name(Variant::Type p_type, int p_constructor, int p_argument) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, String()); + + // STRUCT type doesn't have constructors (yet) + if (p_type == STRUCT) { + return String(); + } ERR_FAIL_INDEX_V(p_constructor, (int)construct_data[p_type].size(), String()); return construct_data[p_type][p_constructor].arg_names[p_argument]; } @@ -328,6 +366,11 @@ String Variant::get_constructor_argument_name(Variant::Type p_type, int p_constr void Variant::get_constructor_list(Type p_type, List *r_list) { ERR_FAIL_INDEX(p_type, Variant::VARIANT_MAX); + // STRUCT type doesn't have constructors (yet) + if (p_type == STRUCT) { + return; + } + MethodInfo mi; mi.return_val.type = p_type; mi.name = get_type_name(p_type); diff --git a/core/variant/variant_internal.h b/core/variant/variant_internal.h index f19f2e2a6d9..c0163f8926d 100644 --- a/core/variant/variant_internal.h +++ b/core/variant/variant_internal.h @@ -123,6 +123,9 @@ class VariantInternal { case Variant::OBJECT: init_object(v); break; + case Variant::STRUCT: + // TODO: Implement struct initialization + break; default: break; } @@ -215,6 +218,10 @@ class VariantInternal { _FORCE_INLINE_ static const ObjectID get_object_id(const Variant *v) { return v->_get_obj().id; } + // Struct access - stores pointer directly in _mem + _FORCE_INLINE_ static void *get_struct(Variant *v) { return v->_data._mem; } + _FORCE_INLINE_ static const void *get_struct(const Variant *v) { return v->_data._mem; } + template _FORCE_INLINE_ static void init_generic(Variant *v) { v->type = GetTypeInfo::VARIANT_TYPE; @@ -438,6 +445,9 @@ class VariantInternal { return get_vector4_array(v); case Variant::OBJECT: return get_object(v); + case Variant::STRUCT: + // TODO: Implement struct pointer access + return nullptr; case Variant::VARIANT_MAX: ERR_FAIL_V(nullptr); } @@ -524,6 +534,9 @@ class VariantInternal { return get_vector4_array(v); case Variant::OBJECT: return get_object(v); + case Variant::STRUCT: + // TODO: Implement struct pointer access + return nullptr; case Variant::VARIANT_MAX: ERR_FAIL_V(nullptr); } @@ -778,6 +791,21 @@ struct VariantGetInternalPtr { static const PackedVector4Array *get_ptr(const Variant *v) { return VariantInternal::get_vector4_array(v); } }; +// Forward declaration for GDScriptStructInstance +class GDScriptStructInstance; + +template <> +struct VariantGetInternalPtr { + static GDScriptStructInstance *get_ptr(Variant *v) { + // STRUCT type stores the pointer directly in _data._mem + return reinterpret_cast(VariantInternal::get_struct(v)); + } + static const GDScriptStructInstance *get_ptr(const Variant *v) { + // STRUCT type stores the pointer directly in _data._mem + return reinterpret_cast(VariantInternal::get_struct(v)); + } +}; + template struct VariantInternalAccessor; diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp index b39ac7defa0..58f46e406c9 100644 --- a/core/variant/variant_setget.cpp +++ b/core/variant/variant_setget.cpp @@ -34,6 +34,7 @@ #include "variant_callable.h" #include "core/io/resource.h" +#include "modules/gdscript/gdscript_struct.h" struct VariantSetterGetterInfo { void (*setter)(Variant *base, const Variant *value, bool &valid); @@ -257,6 +258,13 @@ void Variant::set_named(const StringName &p_member, const Variant &p_value, bool obj->set(p_member, p_value, &r_valid); return; } + } else if (type == Variant::STRUCT) { + GDScriptStructInstance *struct_instance = VariantGetInternalPtr::get_ptr(this); + if (struct_instance) { + r_valid = struct_instance->set(p_member, p_value); + return; + } + r_valid = false; } else if (type == Variant::DICTIONARY) { Dictionary &dict = *VariantGetInternalPtr::get_ptr(this); r_valid = dict.set(p_member, p_value); @@ -288,6 +296,18 @@ Variant Variant::get_named(const StringName &p_member, bool &r_valid) const { return obj->get(p_member, &r_valid); } } break; + case Variant::STRUCT: { + const GDScriptStructInstance *struct_instance = VariantGetInternalPtr::get_ptr(this); + if (struct_instance) { + Variant ret; + if (struct_instance->get(p_member, ret)) { + r_valid = true; + return ret; + } + } + r_valid = false; + return Variant(); + } break; case Variant::DICTIONARY: { const Variant *v = VariantGetInternalPtr::get_ptr(this)->getptr(p_member); if (v) { diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp index 6f0be32e038..5aa6eeeca09 100644 --- a/core/variant/variant_utility.cpp +++ b/core/variant/variant_utility.cpp @@ -917,6 +917,9 @@ Variant VariantUtilityFunctions::type_convert(const Variant &p_variant, const Va return p_variant.operator ::RID(); case Variant::Type::OBJECT: return p_variant.operator Object *(); + case Variant::Type::STRUCT: + // TODO: Implement struct conversion + return p_variant; case Variant::Type::CALLABLE: return p_variant.operator Callable(); case Variant::Type::SIGNAL: @@ -1055,13 +1058,7 @@ void VariantUtilityFunctions::push_warning(const Variant **p_args, int p_arg_cou String VariantUtilityFunctions::var_to_str(const Variant &p_var) { String vars; - VariantWriter::write_to_string(p_var, vars, nullptr, nullptr, true, false); - return vars; -} - -String VariantUtilityFunctions::var_to_str_with_objects(const Variant &p_var) { - String vars; - VariantWriter::write_to_string(p_var, vars, nullptr, nullptr, true, true); + VariantWriter::write_to_string(p_var, vars); return vars; } @@ -1070,27 +1067,9 @@ Variant VariantUtilityFunctions::str_to_var(const String &p_var) { ss.s = p_var; String errs; - int line = 1; - Variant ret; - Error err = VariantParser::parse(&ss, ret, errs, line, nullptr, false); - if (err != OK) { - ERR_PRINT("Parse error at line " + itos(line) + ": " + errs + "."); - } - - return ret; -} - -Variant VariantUtilityFunctions::str_to_var_with_objects(const String &p_var) { - VariantParser::StreamString ss; - ss.s = p_var; - - String errs; - int line = 1; + int line; Variant ret; - Error err = VariantParser::parse(&ss, ret, errs, line, nullptr, true); - if (err != OK) { - ERR_PRINT("Parse error at line " + itos(line) + ": " + errs + "."); - } + (void)VariantParser::parse(&ss, ret, errs, line); return ret; } @@ -1811,9 +1790,6 @@ void Variant::_register_variant_utility_functions() { FUNCBINDR(var_to_str, sarray("variable"), Variant::UTILITY_FUNC_TYPE_GENERAL); FUNCBINDR(str_to_var, sarray("string"), Variant::UTILITY_FUNC_TYPE_GENERAL); - FUNCBINDR(var_to_str_with_objects, sarray("variable"), Variant::UTILITY_FUNC_TYPE_GENERAL); - FUNCBINDR(str_to_var_with_objects, sarray("string"), Variant::UTILITY_FUNC_TYPE_GENERAL); - FUNCBINDR(var_to_bytes, sarray("variable"), Variant::UTILITY_FUNC_TYPE_GENERAL); FUNCBINDR(bytes_to_var, sarray("bytes"), Variant::UTILITY_FUNC_TYPE_GENERAL); diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp index 5004a56adb0..a9ccfad3037 100644 --- a/modules/gdscript/editor/gdscript_docgen.cpp +++ b/modules/gdscript/editor/gdscript_docgen.cpp @@ -149,6 +149,14 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type } } return; + case GDType::STRUCT: + // For now, treat structs as their struct type name + if (p_gdtype.struct_type && p_gdtype.struct_type->identifier) { + r_type = String(p_gdtype.struct_type->identifier->name); + } else { + r_type = "Struct"; + } + return; case GDType::VARIANT: case GDType::RESOLVING: case GDType::UNRESOLVED: diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 1035758076f..ef43b5d7663 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -37,6 +37,7 @@ #include "gdscript_compiler.h" #include "gdscript_parser.h" #include "gdscript_rpc_callable.h" +#include "gdscript_struct.h" #include "gdscript_tokenizer_buffer.h" #include "gdscript_warning.h" @@ -121,6 +122,43 @@ Variant GDScriptNativeClass::callp(const StringName &p_method, const Variant **p return Variant(); } +void GDScriptStructClass::_bind_methods() { + // Don't bind "new" here - it will be handled via callp +} + +GDScriptStructClass::GDScriptStructClass(GDScriptStruct *p_struct) { + struct_type = p_struct; + // Don't ERR_FAIL_NULL here since we need a default constructor for instantiate() +} + +Variant GDScriptStructClass::_new(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + ERR_FAIL_NULL_V(struct_type, Variant()); + GDScriptStructInstance *instance = struct_type->create_instance(p_args, p_argcount); + if (!instance) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + return Variant(); + } + + // Initialize reference count to 1 for the first owner + instance->reference(); + + // Create a Variant of STRUCT type with the instance + Variant result; + result.type = Variant::STRUCT; + // Copy the pointer + memcpy(result._data._mem, &instance, sizeof(instance)); + return result; +} + +Variant GDScriptStructClass::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + if (p_method == SNAME("new")) { + return _new(p_args, p_argcount, r_error); + } + + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + return Variant(); +} + GDScriptFunction *GDScript::_super_constructor(GDScript *p_script) { if (likely(p_script->valid) && p_script->initializer) { return p_script->initializer; @@ -1592,6 +1630,12 @@ void GDScript::clear(ClearData *p_clear_data) { static_variables.clear(); static_variables_indices.clear(); + // Clear structs + for (KeyValue &E : structs) { + clear_data->structs.insert(E.value); + } + structs.clear(); + if (implicit_initializer) { clear_data->functions.insert(implicit_initializer); implicit_initializer = nullptr; @@ -1628,6 +1672,10 @@ void GDScript::clear(ClearData *p_clear_data) { GDScriptCache::remove_script(gdscr->get_path()); } } + // Delete structs + for (GDScriptStruct *E : clear_data->structs) { + memdelete(E); + } clear_data->clear(); } } diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index ebf2565d3af..d6d5f68263c 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -34,6 +34,10 @@ #include "gdscript_function.h" +// Forward declarations +class GDScriptStruct; +class GDScriptStructInstance; + #include "core/debugger/engine_debugger.h" #include "core/debugger/script_debugger.h" #include "core/doc_data.h" @@ -59,6 +63,22 @@ class GDScriptNativeClass : public RefCounted { GDScriptNativeClass(const StringName &p_name); }; +class GDScriptStructClass : public RefCounted { + GDCLASS(GDScriptStructClass, RefCounted); + + GDScriptStruct *struct_type; + +protected: + static void _bind_methods(); + +public: + _FORCE_INLINE_ GDScriptStruct *get_struct_type() const { return struct_type; } + void set_struct_type(GDScriptStruct *p_struct) { struct_type = p_struct; } + Variant _new(const Variant **p_args, int p_argcount, Callable::CallError &r_error); + virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; + GDScriptStructClass(GDScriptStruct *p_struct = nullptr); +}; + class GDScript : public Script { GDCLASS(GDScript, Script); bool tool = false; @@ -77,9 +97,11 @@ class GDScript : public Script { struct ClearData { RBSet functions; RBSet> scripts; + RBSet structs; void clear() { functions.clear(); scripts.clear(); + structs.clear(); } }; @@ -92,6 +114,8 @@ class GDScript : public Script { friend class GDScriptLambdaSelfCallable; friend class GDScriptLanguage; friend struct GDScriptUtilityFunctionsDefinitions; + friend class GDScriptStruct; + friend class GDScriptStructInstance; Ref native; Ref base; @@ -110,6 +134,7 @@ class GDScript : public Script { HashMap member_functions; HashMap> subclasses; HashMap _signals; + HashMap structs; Dictionary rpc_config; public: diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 03ae20c5a71..b3f6bb0d849 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -1256,6 +1256,9 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, case GDScriptParser::ClassNode::Member::GROUP: // No-op, but needed to silence warnings. break; + case GDScriptParser::ClassNode::Member::STRUCT: + // Structs are resolved separately in resolve_struct_body + break; case GDScriptParser::ClassNode::Member::UNDEFINED: ERR_PRINT("Trying to resolve undefined member."); break; @@ -1578,11 +1581,52 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, bo GDScriptParser::ClassNode::Member member = p_class->members[i]; if (member.type == GDScriptParser::ClassNode::Member::CLASS) { resolve_class_body(member.m_class, true); + } else if (member.type == GDScriptParser::ClassNode::Member::STRUCT) { + resolve_struct_body(member.m_struct, true); } } } } +void GDScriptAnalyzer::resolve_struct_body(GDScriptParser::StructNode *p_struct, const GDScriptParser::Node *p_source) { + if (p_struct == nullptr) { + return; + } + + // Resolve base struct if extends is used + if (!p_struct->extends.is_empty()) { + // TODO: Implement struct inheritance resolution + // For now, mark as unresolved + } + + // Resolve field types + for (const GDScriptParser::StructNode::Field &field : p_struct->fields) { + if (field.variable != nullptr && field.variable->datatype_specifier != nullptr) { + resolve_datatype(field.variable->datatype_specifier); + field.variable->set_datatype(field.variable->datatype_specifier->get_datatype()); + } + } + + // Resolve method signatures + for (GDScriptParser::FunctionNode *method : p_struct->methods) { + if (method != nullptr) { + resolve_function_signature(method); + } + } + + // Resolve method bodies + for (GDScriptParser::FunctionNode *method : p_struct->methods) { + if (method != nullptr) { + resolve_function_body(method); + } + } +} + +void GDScriptAnalyzer::resolve_struct_body(GDScriptParser::StructNode *p_struct, bool p_recursive) { + resolve_struct_body(p_struct); + // Recursive resolution not needed for structs since they can't contain nested structs (yet) +} + void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root) { ERR_FAIL_NULL_MSG(p_node, "Trying to resolve type of a null node."); @@ -1596,6 +1640,10 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root resolve_class_body(static_cast(p_node), true); } break; + case GDScriptParser::Node::STRUCT: + // Resolve struct body + resolve_struct_body(static_cast(p_node), true); + break; case GDScriptParser::Node::CONSTANT: resolve_constant(static_cast(p_node), true); break; @@ -2675,6 +2723,7 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre case GDScriptParser::Node::PATTERN: case GDScriptParser::Node::RETURN: case GDScriptParser::Node::SIGNAL: + case GDScriptParser::Node::STRUCT: case GDScriptParser::Node::SUITE: case GDScriptParser::Node::TYPE: case GDScriptParser::Node::VARIABLE: @@ -4972,6 +5021,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::NODE_PATH: case Variant::SIGNAL: case Variant::STRING_NAME: + case Variant::STRUCT: break; // Support depends on if the dictionary has a typed key, otherwise anything is valid. case Variant::DICTIONARY: @@ -5040,6 +5090,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::NODE_PATH: case Variant::SIGNAL: case Variant::STRING_NAME: + case Variant::STRUCT: result_type.kind = GDScriptParser::DataType::VARIANT; push_error(vformat(R"(Cannot use subscript operator on a base of type "%s".)", base_type.to_string()), p_subscript->base); break; @@ -5772,6 +5823,25 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo } } + // Handle struct construction + if (p_base_type.kind == GDScriptParser::DataType::STRUCT) { + if (p_is_constructor && p_function == SNAME("new")) { + // Struct constructor - returns instance of the struct + r_return_type = p_base_type; + r_return_type.is_meta_type = false; + r_method_flags.set_flag(METHOD_FLAG_STATIC); + + // TODO: Get constructor parameters if struct has _init method + // For now, struct constructors have no parameters + + return true; + } + + // TODO: Handle struct methods when implemented + push_error(vformat(R"(Struct "%s" does not have a method "%s".)", p_base_type.to_string(), p_function), p_source); + return false; + } + if (p_base_type.kind == GDScriptParser::DataType::BUILTIN) { // Construct a base type to get methods. Callable::CallError err; @@ -6249,6 +6319,7 @@ bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType & case GDScriptParser::DataType::VARIANT: case GDScriptParser::DataType::BUILTIN: case GDScriptParser::DataType::ENUM: + case GDScriptParser::DataType::STRUCT: case GDScriptParser::DataType::RESOLVING: case GDScriptParser::DataType::UNRESOLVED: break; // Already solved before. @@ -6286,6 +6357,7 @@ bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType & case GDScriptParser::DataType::VARIANT: case GDScriptParser::DataType::BUILTIN: case GDScriptParser::DataType::ENUM: + case GDScriptParser::DataType::STRUCT: case GDScriptParser::DataType::RESOLVING: case GDScriptParser::DataType::UNRESOLVED: break; // Already solved before. diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index b66466012b8..a232c638f50 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -80,6 +80,8 @@ class GDScriptAnalyzer { void resolve_class_interface(GDScriptParser::ClassNode *p_class, bool p_recursive); void resolve_class_body(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source = nullptr); void resolve_class_body(GDScriptParser::ClassNode *p_class, bool p_recursive); + void resolve_struct_body(GDScriptParser::StructNode *p_struct, const GDScriptParser::Node *p_source = nullptr); + void resolve_struct_body(GDScriptParser::StructNode *p_struct, bool p_recursive); void resolve_function_signature(GDScriptParser::FunctionNode *p_function, const GDScriptParser::Node *p_source = nullptr, bool p_is_lambda = false); void resolve_function_body(GDScriptParser::FunctionNode *p_function, bool p_is_lambda = false); void resolve_node(GDScriptParser::Node *p_node, bool p_is_root = true); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 5055b4c3d1e..2bc5cf7d815 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -110,6 +110,8 @@ uint32_t GDScriptByteCodeGenerator::add_temporary(const GDScriptDataType &p_type case Variant::PACKED_VECTOR3_ARRAY: case Variant::PACKED_COLOR_ARRAY: case Variant::PACKED_VECTOR4_ARRAY: + case Variant::STRUCT: + // Structs are reference counted, so we don't use the pool for them. case Variant::VARIANT_MAX: // Arrays, dictionaries, and objects are reference counted, so we don't use the pool for them. temp_type = Variant::NIL; @@ -546,6 +548,9 @@ void GDScriptByteCodeGenerator::write_type_adjust(const Address &p_target, Varia case Variant::PACKED_VECTOR4_ARRAY: append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_VECTOR4_ARRAY); break; + case Variant::STRUCT: + // Structs don't need type adjustment + return; case Variant::NIL: case Variant::VARIANT_MAX: return; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 044e6830d67..0f7163608fa 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -35,6 +35,7 @@ #include "gdscript.h" #include "gdscript_byte_codegen.h" #include "gdscript_cache.h" +#include "gdscript_struct.h" #include "gdscript_utility_functions.h" #include "core/config/engine.h" @@ -194,6 +195,20 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D result.kind = GDScriptDataType::BUILTIN; result.builtin_type = p_datatype.builtin_type; break; + case GDScriptParser::DataType::STRUCT: { + if (p_handle_metatype && p_datatype.is_meta_type) { + // Struct as a type reference + result.kind = GDScriptDataType::BUILTIN; + result.builtin_type = Variant::STRUCT; + break; + } + + // Struct value type + result.kind = GDScriptDataType::STRUCT; + result.builtin_type = Variant::STRUCT; + // TODO: Store reference to struct definition + // result.struct_type = p_datatype.struct_type; + } break; case GDScriptParser::DataType::RESOLVING: case GDScriptParser::DataType::UNRESOLVED: { _set_error("Parser bug (please report): converting unresolved type.", nullptr); @@ -2999,7 +3014,7 @@ Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptP } Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { - // Compile member functions, getters, and setters. + // Compile member functions, getters, setters, and structs. for (int i = 0; i < p_class->members.size(); i++) { const GDScriptParser::ClassNode::Member &member = p_class->members[i]; if (member.type == member.FUNCTION) { @@ -3025,6 +3040,13 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser: } } } + } else if (member.type == member.STRUCT) { + // Compile struct + const GDScriptParser::StructNode *struct_node = member.m_struct; + Error err = _compile_struct(p_script, p_class, struct_node); + if (err) { + return err; + } } } @@ -3128,6 +3150,58 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser: return OK; } +Error GDScriptCompiler::_compile_struct(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::StructNode *p_struct) { + if (p_struct == nullptr) { + return OK; + } + + // Create the GDScriptStruct object + GDScriptStruct *gdstruct = memnew(GDScriptStruct(p_struct->identifier->name)); + gdstruct->set_owner(p_script); + gdstruct->set_fully_qualified_name(p_struct->fqsn); + + // Add members to the struct + for (const GDScriptParser::StructNode::Field &field : p_struct->fields) { + if (field.variable != nullptr) { + Variant::Type type = Variant::NIL; + if (field.variable->datatype_specifier != nullptr) { + type = field.variable->datatype_specifier->get_datatype().builtin_type; + } + gdstruct->add_member(field.variable->identifier->name, type); + } + } + + // Compile struct methods + for (const GDScriptParser::FunctionNode *method : p_struct->methods) { + if (method != nullptr) { + Error err = OK; + _parse_function(err, p_script, p_class, method); + if (err) { + memdelete(gdstruct); + return err; + } + + // Add method to struct + // Note: We'll need to get the compiled function from the script + // For now, this is a placeholder + } + } + + // Register the struct in the script + p_script->structs[p_struct->identifier->name] = gdstruct; + + // Create a wrapper class for struct construction and store it as a constant + // This allows `StructName.new()` to work + Ref struct_wrapper; + struct_wrapper.instantiate(); // This creates the object with ref_count = 1 + struct_wrapper->set_struct_type(gdstruct); // Set the struct type after creation + + // Add to constants so it's accessible at runtime + p_script->constants[p_struct->identifier->name] = struct_wrapper; + + return OK; +} + void GDScriptCompiler::convert_to_initializer_type(Variant &p_variant, const GDScriptParser::VariableNode *p_node) { // Set p_variant to the value of p_node's initializer, with the type of p_node's variable. GDScriptParser::DataType member_t = p_node->datatype; @@ -3149,12 +3223,15 @@ void GDScriptCompiler::make_scripts(GDScript *p_script, const GDScriptParser::Cl p_script->simplified_icon_path = p_class->simplified_icon_path; HashMap> old_subclasses; + HashMap old_structs; if (p_keep_state) { old_subclasses = p_script->subclasses; + old_structs = p_script->structs; } p_script->subclasses.clear(); + p_script->structs.clear(); for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type != GDScriptParser::ClassNode::Member::CLASS) { @@ -3181,6 +3258,18 @@ void GDScriptCompiler::make_scripts(GDScript *p_script, const GDScriptParser::Cl make_scripts(subclass.ptr(), inner_class, p_keep_state); } + + // Clean up old structs that were not reused + // (Structs cannot be reused like subclasses since they may have different definitions) + // First, we need to remove the struct wrapper constants that reference these structs + for (const KeyValue &E : old_structs) { + p_script->constants.erase(E.key); + } + + // Now it's safe to delete the old structs + for (KeyValue &E : old_structs) { + memdelete(E.value); + } } GDScriptCompiler::FunctionLambdaInfo GDScriptCompiler::_get_function_replacement_info(GDScriptFunction *p_func, int p_index, int p_depth, GDScriptFunction *p_parent_func) { diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index ff1ae9a21c4..35487c2c5cc 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -162,6 +162,7 @@ class GDScriptCompiler { Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter); Error _prepare_compilation(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); Error _compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + Error _compile_struct(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::StructNode *p_struct); FunctionLambdaInfo _get_function_replacement_info(GDScriptFunction *p_func, int p_index = -1, int p_depth = 0, GDScriptFunction *p_parent_func = nullptr); Vector _get_function_lambda_replacement_info(GDScriptFunction *p_func, int p_depth = 0, GDScriptFunction *p_parent_func = nullptr); ScriptLambdaInfo _get_script_lambda_replacement_info(GDScript *p_script); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 68575c58ca8..08c938c9efd 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1219,6 +1219,9 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, } option = ScriptLanguage::CodeCompletionOption(member.signal->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); break; + case GDScriptParser::ClassNode::Member::STRUCT: + // TODO: Handle struct completion + break; case GDScriptParser::ClassNode::Member::GROUP: break; // No-op, but silences warnings. case GDScriptParser::ClassNode::Member::UNDEFINED: @@ -2607,6 +2610,9 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & r_type.type.class_type = member.m_class; r_type.type.is_meta_type = true; return true; + case GDScriptParser::ClassNode::Member::STRUCT: + // TODO: Handle struct type inference + return false; case GDScriptParser::ClassNode::Member::GROUP: return false; // No-op, but silences warnings. case GDScriptParser::ClassNode::Member::UNDEFINED: @@ -3980,6 +3986,9 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co case GDScriptParser::ClassNode::Member::SIGNAL: r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_SIGNAL; break; + case GDScriptParser::ClassNode::Member::STRUCT: + // TODO: Handle struct symbol lookup + return ERR_CANT_RESOLVE; case GDScriptParser::ClassNode::Member::VARIABLE: r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY; break; @@ -4284,6 +4293,9 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co return ERR_CANT_RESOLVE; } break; + case GDScriptParser::DataType::STRUCT: + // TODO: Handle struct symbol lookup + return ERR_CANT_RESOLVE; case GDScriptParser::DataType::RESOLVING: case GDScriptParser::DataType::UNRESOLVED: { return ERR_CANT_RESOLVE; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 0e2193a4289..84c43a92642 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -55,6 +55,7 @@ class GDScriptDataType { NATIVE, SCRIPT, GDSCRIPT, + STRUCT, }; Kind kind = UNINITIALIZED; @@ -64,6 +65,7 @@ class GDScriptDataType { StringName native_type; Script *script_type = nullptr; Ref