From 9853a691447cd4e279f48820067174d3833b0065 Mon Sep 17 00:00:00 2001 From: Thaddeus Crews Date: Sat, 24 Jun 2023 13:03:28 -0500 Subject: [PATCH] Implement typed dictionaries --- core/core_constants.cpp | 1 + core/doc_data.cpp | 6 + core/extension/extension_api_dump.cpp | 3 + core/extension/gdextension_interface.cpp | 10 + core/extension/gdextension_interface.h | 16 + core/io/resource_format_binary.cpp | 15 + core/object/object.h | 1 + core/variant/dictionary.cpp | 276 +++++++++++++- core/variant/dictionary.h | 16 + core/variant/typed_dictionary.h | 342 ++++++++++++++++++ core/variant/variant_call.cpp | 13 + core/variant/variant_construct.cpp | 1 + core/variant/variant_construct.h | 106 ++++++ core/variant/variant_parser.cpp | 246 ++++++++++++- core/variant/variant_setget.cpp | 91 +++-- doc/classes/@GlobalScope.xml | 5 +- doc/classes/Dictionary.xml | 95 +++++ doc/tools/make_rst.py | 29 +- editor/connections_dialog.cpp | 16 + editor/doc_tools.cpp | 2 + editor/editor_help.cpp | 22 +- editor/editor_properties.cpp | 2 +- editor/editor_properties_array_dict.cpp | 127 ++++++- editor/editor_properties_array_dict.h | 12 +- modules/gdscript/editor/gdscript_docgen.cpp | 93 ++++- modules/gdscript/gdscript_analyzer.cpp | 308 +++++++++++++++- modules/gdscript/gdscript_analyzer.h | 2 + modules/gdscript/gdscript_byte_codegen.cpp | 84 +++++ modules/gdscript/gdscript_byte_codegen.h | 1 + modules/gdscript/gdscript_codegen.h | 1 + modules/gdscript/gdscript_compiler.cpp | 24 +- modules/gdscript/gdscript_disassembler.cpp | 107 ++++++ modules/gdscript/gdscript_editor.cpp | 4 + modules/gdscript/gdscript_function.h | 43 +++ modules/gdscript/gdscript_parser.cpp | 218 ++++++++++- modules/gdscript/gdscript_parser.h | 2 + modules/gdscript/gdscript_vm.cpp | 194 +++++++++- ..._specified_type_with_literal_dictionary.gd | 3 + ...specified_type_with_literal_dictionary.out | 2 + ...ped_dictionary_assign_differently_typed.gd | 4 + ...ed_dictionary_assign_differently_typed.out | 2 + .../errors/typed_dictionary_assignment.gd | 2 + .../errors/typed_dictionary_assignment.out | 2 + ...nary_init_with_unconvertible_in_literal.gd | 4 + ...ary_init_with_unconvertible_in_literal.out | 2 + ...ed_dictionary_pass_differently_to_typed.gd | 7 + ...d_dictionary_pass_differently_to_typed.out | 2 + .../typed_dictionary_as_default_parameter.gd | 20 + .../typed_dictionary_as_default_parameter.out | 11 + ...ictionary_inferred_access_isnt_constant.gd | 4 + ...ctionary_inferred_access_isnt_constant.out | 2 + .../features/typed_dictionary_usage.gd | 214 +++++++++++ .../features/typed_dictionary_usage.out | 2 + .../typed_dictionary_with_custom_class.gd | 9 + .../typed_dictionary_with_custom_class.out | 2 + .../parser/features/typed_dictionaries.gd | 5 + .../parser/features/typed_dictionaries.out | 3 + .../typed_dictionary_assign_basic_to_typed.gd | 4 + ...typed_dictionary_assign_basic_to_typed.out | 6 + ...ped_dictionary_assign_differently_typed.gd | 4 + ...ed_dictionary_assign_differently_typed.out | 6 + .../typed_dictionary_assign_wrong_to_typed.gd | 7 + ...typed_dictionary_assign_wrong_to_typed.out | 5 + .../typed_dictionary_pass_basic_to_typed.gd | 7 + .../typed_dictionary_pass_basic_to_typed.out | 6 + ...ed_dictionary_pass_differently_to_typed.gd | 7 + ...d_dictionary_pass_differently_to_typed.out | 6 + .../for_loop_iterator_specified_types.gd | 9 +- .../for_loop_iterator_specified_types.out | 4 + .../scripts/runtime/features/member_info.gd | 24 +- .../scripts/runtime/features/member_info.out | 24 +- .../typed_dictionary_implicit_cast_param.gd | 6 + .../typed_dictionary_implicit_cast_param.out | 3 + ...dictionary_init_with_untyped_in_literal.gd | 7 + ...ictionary_init_with_untyped_in_literal.out | 2 + .../gdscript/tests/scripts/utils.notest.gd | 7 + ...portedFields_ScriptProperties.generated.cs | 2 +- ...edProperties_ScriptProperties.generated.cs | 2 +- .../Godot.SourceGenerators/GodotEnums.cs | 3 +- .../Godot.SourceGenerators/MarshalUtils.cs | 8 + .../ScriptPropertiesGenerator.cs | 69 +++- modules/mono/editor/bindings_generator.cpp | 15 + scene/resources/packed_scene.cpp | 117 +++++- scene/resources/packed_scene.h | 1 + scene/resources/resource_format_text.cpp | 28 ++ tests/core/variant/test_dictionary.h | 39 +- 86 files changed, 3071 insertions(+), 193 deletions(-) create mode 100644 core/variant/typed_dictionary.h create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out create mode 100644 modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd create mode 100644 modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out create mode 100644 modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd create mode 100644 modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out create mode 100644 modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd create mode 100644 modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out create mode 100644 modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd create mode 100644 modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out create mode 100644 modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd create mode 100644 modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out create mode 100644 modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd create mode 100644 modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out create mode 100644 modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd create mode 100644 modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out create mode 100644 modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd create mode 100644 modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out diff --git a/core/core_constants.cpp b/core/core_constants.cpp index 5322e39ec0d..68af5abf66a 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -671,6 +671,7 @@ void register_global_constants() { BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_OBJECTID); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_INT_IS_POINTER); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_ARRAY_TYPE); + BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_DICTIONARY_TYPE); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALE_ID); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LOCALIZABLE_STRING); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_NODE_TYPE); diff --git a/core/doc_data.cpp b/core/doc_data.cpp index 672a36c35c1..f40e878d52b 100644 --- a/core/doc_data.cpp +++ b/core/doc_data.cpp @@ -33,6 +33,8 @@ String DocData::get_default_value_string(const Variant &p_value) { if (p_value.get_type() == Variant::ARRAY) { return Variant(Array(p_value, 0, StringName(), Variant())).get_construct_string().replace("\n", " "); + } else if (p_value.get_type() == Variant::DICTIONARY) { + return Variant(Dictionary(p_value, 0, StringName(), Variant(), 0, StringName(), Variant())).get_construct_string().replace("\n", " "); } else { return p_value.get_construct_string().replace("\n", " "); } @@ -57,6 +59,8 @@ void DocData::return_doc_from_retinfo(DocData::MethodDoc &p_method, const Proper p_method.return_type = p_retinfo.class_name; } else if (p_retinfo.type == Variant::ARRAY && p_retinfo.hint == PROPERTY_HINT_ARRAY_TYPE) { p_method.return_type = p_retinfo.hint_string + "[]"; + } else if (p_retinfo.type == Variant::DICTIONARY && p_retinfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) { + p_method.return_type = "Dictionary[" + p_retinfo.hint_string.replace(";", ", ") + "]"; } else if (p_retinfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { p_method.return_type = p_retinfo.hint_string; } else if (p_retinfo.type == Variant::NIL && p_retinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { @@ -89,6 +93,8 @@ void DocData::argument_doc_from_arginfo(DocData::ArgumentDoc &p_argument, const p_argument.type = p_arginfo.class_name; } else if (p_arginfo.type == Variant::ARRAY && p_arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) { p_argument.type = p_arginfo.hint_string + "[]"; + } else if (p_arginfo.type == Variant::DICTIONARY && p_arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) { + p_argument.type = "Dictionary[" + p_arginfo.hint_string.replace(";", ", ") + "]"; } else if (p_arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { p_argument.type = p_arginfo.hint_string; } else if (p_arginfo.type == Variant::NIL) { diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp index 848b6f3886e..296ebc901f8 100644 --- a/core/extension/extension_api_dump.cpp +++ b/core/extension/extension_api_dump.cpp @@ -60,6 +60,9 @@ static String get_property_info_type_name(const PropertyInfo &p_info) { if (p_info.type == Variant::ARRAY && (p_info.hint == PROPERTY_HINT_ARRAY_TYPE)) { return String("typedarray::") + p_info.hint_string; } + if (p_info.type == Variant::DICTIONARY && (p_info.hint == PROPERTY_HINT_DICTIONARY_TYPE)) { + return String("typeddictionary::") + p_info.hint_string; + } if (p_info.type == Variant::INT && (p_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM))) { return String("enum::") + String(p_info.class_name); } diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index 0ebe86d0a71..ddf90f61307 100644 --- a/core/extension/gdextension_interface.cpp +++ b/core/extension/gdextension_interface.cpp @@ -1199,6 +1199,15 @@ static GDExtensionVariantPtr gdextension_dictionary_operator_index_const(GDExten return (GDExtensionVariantPtr)&self->operator[](*(const Variant *)p_key); } +void gdextension_dictionary_set_typed(GDExtensionTypePtr p_self, GDExtensionVariantType p_key_type, GDExtensionConstStringNamePtr p_key_class_name, GDExtensionConstVariantPtr p_key_script, GDExtensionVariantType p_value_type, GDExtensionConstStringNamePtr p_value_class_name, GDExtensionConstVariantPtr p_value_script) { + Dictionary *self = reinterpret_cast(p_self); + const StringName *key_class_name = reinterpret_cast(p_key_class_name); + const Variant *key_script = reinterpret_cast(p_key_script); + const StringName *value_class_name = reinterpret_cast(p_value_class_name); + const Variant *value_script = reinterpret_cast(p_value_script); + self->set_typed((uint32_t)p_key_type, *key_class_name, *key_script, (uint32_t)p_value_type, *value_class_name, *value_script); +} + /* OBJECT API */ static void gdextension_object_method_bind_call(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_arg_count, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error) { @@ -1679,6 +1688,7 @@ void gdextension_setup_interface() { REGISTER_INTERFACE_FUNC(array_set_typed); REGISTER_INTERFACE_FUNC(dictionary_operator_index); REGISTER_INTERFACE_FUNC(dictionary_operator_index_const); + REGISTER_INTERFACE_FUNC(dictionary_set_typed); REGISTER_INTERFACE_FUNC(object_method_bind_call); REGISTER_INTERFACE_FUNC(object_method_bind_ptrcall); REGISTER_INTERFACE_FUNC(object_destroy); diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index 9057e04bf32..d3132baf1b4 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -2372,6 +2372,22 @@ typedef GDExtensionVariantPtr (*GDExtensionInterfaceDictionaryOperatorIndex)(GDE */ typedef GDExtensionVariantPtr (*GDExtensionInterfaceDictionaryOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionConstVariantPtr p_key); +/** + * @name dictionary_set_typed + * @since 4.4 + * + * Makes a Dictionary into a typed Dictionary. + * + * @param p_self A pointer to the Dictionary. + * @param p_key_type The type of Variant the Dictionary key will store. + * @param p_key_class_name A pointer to a StringName with the name of the object (if p_key_type is GDEXTENSION_VARIANT_TYPE_OBJECT). + * @param p_key_script A pointer to a Script object (if p_key_type is GDEXTENSION_VARIANT_TYPE_OBJECT and the base class is extended by a script). + * @param p_value_type The type of Variant the Dictionary value will store. + * @param p_value_class_name A pointer to a StringName with the name of the object (if p_value_type is GDEXTENSION_VARIANT_TYPE_OBJECT). + * @param p_value_script A pointer to a Script object (if p_value_type is GDEXTENSION_VARIANT_TYPE_OBJECT and the base class is extended by a script). + */ +typedef void (*GDExtensionInterfaceDictionarySetTyped)(GDExtensionTypePtr p_self, GDExtensionVariantType p_key_type, GDExtensionConstStringNamePtr p_key_class_name, GDExtensionConstVariantPtr p_key_script, GDExtensionVariantType p_value_type, GDExtensionConstStringNamePtr p_value_class_name, GDExtensionConstVariantPtr p_value_script); + /* INTERFACE: Object */ /** diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index f71257fa764..41a8a569d0e 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -857,6 +857,19 @@ Error ResourceLoaderBinary::load() { } } + if (value.get_type() == Variant::DICTIONARY) { + Dictionary set_dict = value; + bool is_get_valid = false; + Variant get_value = res->get(name, &is_get_valid); + if (is_get_valid && get_value.get_type() == Variant::DICTIONARY) { + Dictionary get_dict = get_value; + if (!set_dict.is_same_typed(get_dict)) { + value = Dictionary(set_dict, get_dict.get_typed_key_builtin(), get_dict.get_typed_key_class_name(), get_dict.get_typed_key_script(), + get_dict.get_typed_value_builtin(), get_dict.get_typed_value_class_name(), get_dict.get_typed_value_script()); + } + } + } + if (set_valid) { res->set(name, value); } @@ -2064,6 +2077,8 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant case Variant::DICTIONARY: { Dictionary d = p_variant; + _find_resources(d.get_typed_key_script()); + _find_resources(d.get_typed_value_script()); List keys; d.get_key_list(&keys); for (const Variant &E : keys) { diff --git a/core/object/object.h b/core/object/object.h index bc3f663baf9..19e6fc5d475 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -86,6 +86,7 @@ enum PropertyHint { PROPERTY_HINT_HIDE_QUATERNION_EDIT, /// Only Node3D::transform should hide the quaternion editor. PROPERTY_HINT_PASSWORD, PROPERTY_HINT_LAYERS_AVOIDANCE, + PROPERTY_HINT_DICTIONARY_TYPE, PROPERTY_HINT_MAX, }; diff --git a/core/variant/dictionary.cpp b/core/variant/dictionary.cpp index 733d13a106e..f2522a4545c 100644 --- a/core/variant/dictionary.cpp +++ b/core/variant/dictionary.cpp @@ -32,6 +32,7 @@ #include "core/templates/hash_map.h" #include "core/templates/safe_refcount.h" +#include "core/variant/container_type_validate.h" #include "core/variant/variant.h" // required in this order by VariantInternal, do not remove this comment. #include "core/object/class_db.h" @@ -43,6 +44,9 @@ struct DictionaryPrivate { SafeRefCount refcount; Variant *read_only = nullptr; // If enabled, a pointer is used to a temporary value that is used to return read-only values. HashMap variant_map; + ContainerTypeValidate typed_key; + ContainerTypeValidate typed_value; + Variant *typed_fallback = nullptr; // Allows a typed dictionary to return dummy values when attempting an invalid access. }; void Dictionary::get_key_list(List *p_keys) const { @@ -120,7 +124,9 @@ Variant *Dictionary::getptr(const Variant &p_key) { } Variant Dictionary::get_valid(const Variant &p_key) const { - HashMap::ConstIterator E(_p->variant_map.find(p_key)); + Variant key = p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "get_valid"), Variant()); + HashMap::ConstIterator E(_p->variant_map.find(key)); if (!E) { return Variant(); @@ -129,7 +135,9 @@ Variant Dictionary::get_valid(const Variant &p_key) const { } Variant Dictionary::get(const Variant &p_key, const Variant &p_default) const { - const Variant *result = getptr(p_key); + Variant key = p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "get"), p_default); + const Variant *result = getptr(key); if (!result) { return p_default; } @@ -138,10 +146,14 @@ Variant Dictionary::get(const Variant &p_key, const Variant &p_default) const { } Variant Dictionary::get_or_add(const Variant &p_key, const Variant &p_default) { - const Variant *result = getptr(p_key); + Variant key = p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "get"), p_default); + const Variant *result = getptr(key); if (!result) { - operator[](p_key) = p_default; - return p_default; + Variant value = p_default; + ERR_FAIL_COND_V(!_p->typed_value.validate(value, "add"), value); + operator[](key) = value; + return value; } return *result; } @@ -155,12 +167,16 @@ bool Dictionary::is_empty() const { } bool Dictionary::has(const Variant &p_key) const { + Variant key = p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "use 'has'"), false); return _p->variant_map.has(p_key); } bool Dictionary::has_all(const Array &p_keys) const { for (int i = 0; i < p_keys.size(); i++) { - if (!has(p_keys[i])) { + Variant key = p_keys[i]; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "use 'has_all'"), false); + if (!has(key)) { return false; } } @@ -168,8 +184,10 @@ bool Dictionary::has_all(const Array &p_keys) const { } Variant Dictionary::find_key(const Variant &p_value) const { + Variant value = p_value; + ERR_FAIL_COND_V(!_p->typed_value.validate(value, "find_key"), Variant()); for (const KeyValue &E : _p->variant_map) { - if (E.value == p_value) { + if (E.value == value) { return E.key; } } @@ -177,8 +195,10 @@ Variant Dictionary::find_key(const Variant &p_value) const { } bool Dictionary::erase(const Variant &p_key) { + Variant key = p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "erase"), false); ERR_FAIL_COND_V_MSG(_p->read_only, false, "Dictionary is in read-only state."); - return _p->variant_map.erase(p_key); + return _p->variant_map.erase(key); } bool Dictionary::operator==(const Dictionary &p_dictionary) const { @@ -238,8 +258,12 @@ void Dictionary::clear() { void Dictionary::merge(const Dictionary &p_dictionary, bool p_overwrite) { ERR_FAIL_COND_MSG(_p->read_only, "Dictionary is in read-only state."); for (const KeyValue &E : p_dictionary._p->variant_map) { - if (p_overwrite || !has(E.key)) { - operator[](E.key) = E.value; + Variant key = E.key; + Variant value = E.value; + ERR_FAIL_COND(!_p->typed_key.validate(key, "merge")); + ERR_FAIL_COND(!_p->typed_key.validate(value, "merge")); + if (p_overwrite || !has(key)) { + operator[](key) = value; } } } @@ -256,6 +280,9 @@ void Dictionary::_unref() const { if (_p->read_only) { memdelete(_p->read_only); } + if (_p->typed_fallback) { + memdelete(_p->typed_fallback); + } memdelete(_p); } _p = nullptr; @@ -284,6 +311,9 @@ uint32_t Dictionary::recursive_hash(int recursion_count) const { Array Dictionary::keys() const { Array varr; + if (is_typed_key()) { + varr.set_typed(get_typed_key_builtin(), get_typed_key_class_name(), get_typed_key_script()); + } if (_p->variant_map.is_empty()) { return varr; } @@ -301,6 +331,9 @@ Array Dictionary::keys() const { Array Dictionary::values() const { Array varr; + if (is_typed_value()) { + varr.set_typed(get_typed_value_builtin(), get_typed_value_class_name(), get_typed_value_script()); + } if (_p->variant_map.is_empty()) { return varr; } @@ -316,6 +349,146 @@ Array Dictionary::values() const { return varr; } +void Dictionary::assign(const Dictionary &p_dictionary) { + const ContainerTypeValidate &typed_key = _p->typed_key; + const ContainerTypeValidate &typed_key_source = p_dictionary._p->typed_key; + + const ContainerTypeValidate &typed_value = _p->typed_value; + const ContainerTypeValidate &typed_value_source = p_dictionary._p->typed_value; + + if ((typed_key == typed_key_source || typed_key.type == Variant::NIL || (typed_key_source.type == Variant::OBJECT && typed_key.can_reference(typed_key_source))) && + (typed_value == typed_value_source || typed_value.type == Variant::NIL || (typed_value_source.type == Variant::OBJECT && typed_value.can_reference(typed_value_source)))) { + // From same to same or, + // from anything to variants or, + // from subclasses to base classes. + _p->variant_map = p_dictionary._p->variant_map; + return; + } + + int size = p_dictionary._p->variant_map.size(); + HashMap variant_map = HashMap(size); + + Vector key_array; + key_array.resize(size); + Variant *key_data = key_array.ptrw(); + + Vector value_array; + value_array.resize(size); + Variant *value_data = value_array.ptrw(); + + if (typed_key == typed_key_source || typed_key.type == Variant::NIL || (typed_key_source.type == Variant::OBJECT && typed_key.can_reference(typed_key_source))) { + // From same to same or, + // from anything to variants or, + // from subclasses to base classes. + int i = 0; + for (const KeyValue &E : p_dictionary._p->variant_map) { + const Variant *key = &E.key; + key_data[i++] = *key; + } + } else if ((typed_key_source.type == Variant::NIL && typed_key.type == Variant::OBJECT) || (typed_key_source.type == Variant::OBJECT && typed_key_source.can_reference(typed_key))) { + // From variants to objects or, + // from base classes to subclasses. + int i = 0; + for (const KeyValue &E : p_dictionary._p->variant_map) { + const Variant *key = &E.key; + if (key->get_type() != Variant::NIL && (key->get_type() != Variant::OBJECT || !typed_key.validate_object(*key, "assign"))) { + ERR_FAIL_MSG(vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type))); + } + key_data[i++] = *key; + } + } else if (typed_key.type == Variant::OBJECT || typed_key_source.type == Variant::OBJECT) { + ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s]".)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type), + Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type))); + } else if (typed_key_source.type == Variant::NIL && typed_key.type != Variant::OBJECT) { + // From variants to primitives. + int i = 0; + for (const KeyValue &E : p_dictionary._p->variant_map) { + const Variant *key = &E.key; + if (key->get_type() == typed_key.type) { + key_data[i++] = *key; + continue; + } + if (!Variant::can_convert_strict(key->get_type(), typed_key.type)) { + ERR_FAIL_MSG(vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type))); + } + Callable::CallError ce; + Variant::construct(typed_key.type, key_data[i++], &key, 1, ce); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type))); + } + } else if (Variant::can_convert_strict(typed_key_source.type, typed_key.type)) { + // From primitives to different convertible primitives. + int i = 0; + for (const KeyValue &E : p_dictionary._p->variant_map) { + const Variant *key = &E.key; + Callable::CallError ce; + Variant::construct(typed_key.type, key_data[i++], &key, 1, ce); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type))); + } + } else { + ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s].)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type), + Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type))); + } + + if (typed_value == typed_value_source || typed_value.type == Variant::NIL || (typed_value_source.type == Variant::OBJECT && typed_value.can_reference(typed_value_source))) { + // From same to same or, + // from anything to variants or, + // from subclasses to base classes. + int i = 0; + for (const KeyValue &E : p_dictionary._p->variant_map) { + const Variant *value = &E.value; + value_data[i++] = *value; + } + } else if (((typed_value_source.type == Variant::NIL && typed_value.type == Variant::OBJECT) || (typed_value_source.type == Variant::OBJECT && typed_value_source.can_reference(typed_value)))) { + // From variants to objects or, + // from base classes to subclasses. + int i = 0; + for (const KeyValue &E : p_dictionary._p->variant_map) { + const Variant *value = &E.value; + if (value->get_type() != Variant::NIL && (value->get_type() != Variant::OBJECT || !typed_value.validate_object(*value, "assign"))) { + ERR_FAIL_MSG(vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type))); + } + value_data[i++] = *value; + } + } else if (typed_value.type == Variant::OBJECT || typed_value_source.type == Variant::OBJECT) { + ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s]".)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type), + Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type))); + } else if (typed_value_source.type == Variant::NIL && typed_value.type != Variant::OBJECT) { + // From variants to primitives. + int i = 0; + for (const KeyValue &E : p_dictionary._p->variant_map) { + const Variant *value = &E.value; + if (value->get_type() == typed_value.type) { + value_data[i++] = *value; + continue; + } + if (!Variant::can_convert_strict(value->get_type(), typed_value.type)) { + ERR_FAIL_MSG(vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type))); + } + Callable::CallError ce; + Variant::construct(typed_value.type, value_data[i++], &value, 1, ce); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i - 1], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type))); + } + } else if (Variant::can_convert_strict(typed_value_source.type, typed_value.type)) { + // From primitives to different convertible primitives. + int i = 0; + for (const KeyValue &E : p_dictionary._p->variant_map) { + const Variant *value = &E.value; + Callable::CallError ce; + Variant::construct(typed_value.type, value_data[i++], &value, 1, ce); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i - 1], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type))); + } + } else { + ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s].)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type), + Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type))); + } + + for (int i = 0; i < size; i++) { + variant_map.insert(key_data[i], value_data[i]); + } + + _p->variant_map = variant_map; +} + const Variant *Dictionary::next(const Variant *p_key) const { if (p_key == nullptr) { // caller wants to get the first element @@ -324,7 +497,9 @@ const Variant *Dictionary::next(const Variant *p_key) const { } return nullptr; } - HashMap::Iterator E = _p->variant_map.find(*p_key); + Variant key = *p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "next"), nullptr); + HashMap::Iterator E = _p->variant_map.find(key); if (!E) { return nullptr; @@ -354,6 +529,8 @@ bool Dictionary::is_read_only() const { Dictionary Dictionary::recursive_duplicate(bool p_deep, int recursion_count) const { Dictionary n; + n._p->typed_key = _p->typed_key; + n._p->typed_value = _p->typed_value; if (recursion_count > MAX_RECURSION) { ERR_PRINT("Max recursion reached"); @@ -374,6 +551,76 @@ Dictionary Dictionary::recursive_duplicate(bool p_deep, int recursion_count) con return n; } +void Dictionary::set_typed(uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script) { + ERR_FAIL_COND_MSG(_p->read_only, "Dictionary is in read-only state."); + ERR_FAIL_COND_MSG(_p->variant_map.size() > 0, "Type can only be set when dictionary is empty."); + ERR_FAIL_COND_MSG(_p->refcount.get() > 1, "Type can only be set when dictionary has no more than one user."); + ERR_FAIL_COND_MSG(_p->typed_key.type != Variant::NIL || _p->typed_value.type != Variant::NIL, "Type can only be set once."); + ERR_FAIL_COND_MSG((p_key_class_name != StringName() && p_key_type != Variant::OBJECT) || (p_value_class_name != StringName() && p_value_type != Variant::OBJECT), "Class names can only be set for type OBJECT."); + Ref