Implement typed dictionaries

This commit is contained in:
Thaddeus Crews 2023-06-24 13:03:28 -05:00
parent 906a4e9db9
commit 9853a69144
No known key found for this signature in database
GPG Key ID: 62181B86FE9E5D84
86 changed files with 3071 additions and 193 deletions

View File

@ -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);

View File

@ -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) {

View File

@ -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);
}

View File

@ -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<Dictionary *>(p_self);
const StringName *key_class_name = reinterpret_cast<const StringName *>(p_key_class_name);
const Variant *key_script = reinterpret_cast<const Variant *>(p_key_script);
const StringName *value_class_name = reinterpret_cast<const StringName *>(p_value_class_name);
const Variant *value_script = reinterpret_cast<const Variant *>(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);

View File

@ -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 */
/**

View File

@ -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<Variant> keys;
d.get_key_list(&keys);
for (const Variant &E : keys) {

View File

@ -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,
};

View File

@ -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, Variant, VariantHasher, StringLikeVariantComparator> 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<Variant> *p_keys) const {
@ -120,7 +124,9 @@ Variant *Dictionary::getptr(const Variant &p_key) {
}
Variant Dictionary::get_valid(const Variant &p_key) const {
HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::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<Variant, Variant, VariantHasher, StringLikeVariantComparator>::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<Variant, Variant> &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<Variant, Variant> &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, Variant, VariantHasher, StringLikeVariantComparator> variant_map = HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>(size);
Vector<Variant> key_array;
key_array.resize(size);
Variant *key_data = key_array.ptrw();
Vector<Variant> 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<Variant, Variant> &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<Variant, Variant> &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<Variant, Variant> &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<Variant, Variant> &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<Variant, Variant> &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<Variant, Variant> &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<Variant, Variant> &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<Variant, Variant> &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<Variant, Variant, VariantHasher, StringLikeVariantComparator>::Iterator E = _p->variant_map.find(*p_key);
Variant key = *p_key;
ERR_FAIL_COND_V(!_p->typed_key.validate(key, "next"), nullptr);
HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::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<Script> key_script = p_key_script;
ERR_FAIL_COND_MSG(key_script.is_valid() && p_key_class_name == StringName(), "Script class can only be set together with base class name.");
Ref<Script> value_script = p_value_script;
ERR_FAIL_COND_MSG(value_script.is_valid() && p_value_class_name == StringName(), "Script class can only be set together with base class name.");
_p->typed_key.type = Variant::Type(p_key_type);
_p->typed_key.class_name = p_key_class_name;
_p->typed_key.script = key_script;
_p->typed_key.where = "TypedDictionary.Key";
_p->typed_value.type = Variant::Type(p_value_type);
_p->typed_value.class_name = p_value_class_name;
_p->typed_value.script = value_script;
_p->typed_value.where = "TypedDictionary.Value";
}
bool Dictionary::is_typed() const {
return is_typed_key() || is_typed_value();
}
bool Dictionary::is_typed_key() const {
return _p->typed_key.type != Variant::NIL;
}
bool Dictionary::is_typed_value() const {
return _p->typed_value.type != Variant::NIL;
}
bool Dictionary::is_same_typed(const Dictionary &p_other) const {
return is_same_typed_key(p_other) && is_same_typed_value(p_other);
}
bool Dictionary::is_same_typed_key(const Dictionary &p_other) const {
return _p->typed_key == p_other._p->typed_key;
}
bool Dictionary::is_same_typed_value(const Dictionary &p_other) const {
return _p->typed_value == p_other._p->typed_value;
}
uint32_t Dictionary::get_typed_key_builtin() const {
return _p->typed_key.type;
}
uint32_t Dictionary::get_typed_value_builtin() const {
return _p->typed_value.type;
}
StringName Dictionary::get_typed_key_class_name() const {
return _p->typed_key.class_name;
}
StringName Dictionary::get_typed_value_class_name() const {
return _p->typed_value.class_name;
}
Variant Dictionary::get_typed_key_script() const {
return _p->typed_key.script;
}
Variant Dictionary::get_typed_value_script() const {
return _p->typed_value.script;
}
void Dictionary::operator=(const Dictionary &p_dictionary) {
if (this == &p_dictionary) {
return;
@ -385,6 +632,13 @@ const void *Dictionary::id() const {
return _p;
}
Dictionary::Dictionary(const Dictionary &p_base, 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) {
_p = memnew(DictionaryPrivate);
_p->refcount.init();
set_typed(p_key_type, p_key_class_name, p_key_script, p_value_type, p_value_class_name, p_value_script);
assign(p_base);
}
Dictionary::Dictionary(const Dictionary &p_from) {
_p = nullptr;
_ref(p_from);

View File

@ -80,6 +80,7 @@ public:
uint32_t recursive_hash(int recursion_count) const;
void operator=(const Dictionary &p_dictionary);
void assign(const Dictionary &p_dictionary);
const Variant *next(const Variant *p_key = nullptr) const;
Array keys() const;
@ -88,11 +89,26 @@ public:
Dictionary duplicate(bool p_deep = false) const;
Dictionary recursive_duplicate(bool p_deep, int recursion_count) const;
void 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);
bool is_typed() const;
bool is_typed_key() const;
bool is_typed_value() const;
bool is_same_typed(const Dictionary &p_other) const;
bool is_same_typed_key(const Dictionary &p_other) const;
bool is_same_typed_value(const Dictionary &p_other) const;
uint32_t get_typed_key_builtin() const;
uint32_t get_typed_value_builtin() const;
StringName get_typed_key_class_name() const;
StringName get_typed_value_class_name() const;
Variant get_typed_key_script() const;
Variant get_typed_value_script() const;
void make_read_only();
bool is_read_only() const;
const void *id() const;
Dictionary(const Dictionary &p_base, 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);
Dictionary(const Dictionary &p_from);
Dictionary();
~Dictionary();

View File

@ -0,0 +1,342 @@
/**************************************************************************/
/* typed_dictionary.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef TYPED_DICTIONARY_H
#define TYPED_DICTIONARY_H
#include "core/object/object.h"
#include "core/variant/binder_common.h"
#include "core/variant/dictionary.h"
#include "core/variant/method_ptrcall.h"
#include "core/variant/type_info.h"
#include "core/variant/variant.h"
template <typename K, typename V>
class TypedDictionary : public Dictionary {
public:
_FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) {
ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign a dictionary with a different element type.");
Dictionary::operator=(p_dictionary);
}
_FORCE_INLINE_ TypedDictionary(const Variant &p_variant) :
TypedDictionary(Dictionary(p_variant)) {
}
_FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) {
set_typed(Variant::OBJECT, K::get_class_static(), Variant(), Variant::OBJECT, V::get_class_static(), Variant());
if (is_same_typed(p_dictionary)) {
Dictionary::operator=(p_dictionary);
} else {
assign(p_dictionary);
}
}
_FORCE_INLINE_ TypedDictionary() {
set_typed(Variant::OBJECT, K::get_class_static(), Variant(), Variant::OBJECT, V::get_class_static(), Variant());
}
};
template <typename K, typename V>
struct VariantInternalAccessor<TypedDictionary<K, V>> {
static _FORCE_INLINE_ TypedDictionary<K, V> get(const Variant *v) { return *VariantInternal::get_dictionary(v); }
static _FORCE_INLINE_ void set(Variant *v, const TypedDictionary<K, V> &p_dictionary) { *VariantInternal::get_dictionary(v) = p_dictionary; }
};
template <typename K, typename V>
struct VariantInternalAccessor<const TypedDictionary<K, V> &> {
static _FORCE_INLINE_ TypedDictionary<K, V> get(const Variant *v) { return *VariantInternal::get_dictionary(v); }
static _FORCE_INLINE_ void set(Variant *v, const TypedDictionary<K, V> &p_dictionary) { *VariantInternal::get_dictionary(v) = p_dictionary; }
};
template <typename K, typename V>
struct PtrToArg<TypedDictionary<K, V>> {
_FORCE_INLINE_ static TypedDictionary<K, V> convert(const void *p_ptr) {
return TypedDictionary<K, V>(*reinterpret_cast<const Dictionary *>(p_ptr));
}
typedef Dictionary EncodeT;
_FORCE_INLINE_ static void encode(TypedDictionary<K, V> p_val, void *p_ptr) {
*(Dictionary *)p_ptr = p_val;
}
};
template <typename K, typename V>
struct PtrToArg<const TypedDictionary<K, V> &> {
typedef Dictionary EncodeT;
_FORCE_INLINE_ static TypedDictionary<K, V>
convert(const void *p_ptr) {
return TypedDictionary<K, V>(*reinterpret_cast<const Dictionary *>(p_ptr));
}
};
template <typename K, typename V>
struct GetTypeInfo<TypedDictionary<K, V>> {
static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY;
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
static inline PropertyInfo get_class_info() {
return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, vformat("%s;%s", K::get_class_static(), V::get_class_static()));
}
};
template <typename K, typename V>
struct GetTypeInfo<const TypedDictionary<K, V> &> {
static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY;
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE;
static inline PropertyInfo get_class_info() {
return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, vformat("%s;%s", K::get_class_static(), V::get_class_static()));
}
};
// Specialization for the rest of the Variant types.
#define MAKE_TYPED_DICTIONARY_WITH_OBJECT(m_type, m_variant_type) \
template <typename T> \
class TypedDictionary<T, m_type> : public Dictionary { \
public: \
_FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) { \
ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign an dictionary with a different element type."); \
Dictionary::operator=(p_dictionary); \
} \
_FORCE_INLINE_ TypedDictionary(const Variant &p_variant) : \
TypedDictionary(Dictionary(p_variant)) { \
} \
_FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) { \
set_typed(Variant::OBJECT, T::get_class_static(), Variant(), m_variant_type, StringName(), Variant()); \
if (is_same_typed(p_dictionary)) { \
Dictionary::operator=(p_dictionary); \
} else { \
assign(p_dictionary); \
} \
} \
_FORCE_INLINE_ TypedDictionary() { \
set_typed(Variant::OBJECT, T::get_class_static(), Variant(), m_variant_type, StringName(), Variant()); \
} \
}; \
template <typename T> \
struct GetTypeInfo<TypedDictionary<T, m_type>> { \
static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \
static inline PropertyInfo get_class_info() { \
return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \
vformat("%s;%s", T::get_class_static(), m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type))); \
} \
}; \
template <typename T> \
struct GetTypeInfo<const TypedDictionary<T, m_type> &> { \
static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \
static inline PropertyInfo get_class_info() { \
return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \
vformat("%s;%s", T::get_class_static(), m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type))); \
} \
}; \
template <typename T> \
class TypedDictionary<m_type, T> : public Dictionary { \
public: \
_FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) { \
ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign an dictionary with a different element type."); \
Dictionary::operator=(p_dictionary); \
} \
_FORCE_INLINE_ TypedDictionary(const Variant &p_variant) : \
TypedDictionary(Dictionary(p_variant)) { \
} \
_FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) { \
set_typed(m_variant_type, StringName(), Variant(), Variant::OBJECT, T::get_class_static(), Variant()); \
if (is_same_typed(p_dictionary)) { \
Dictionary::operator=(p_dictionary); \
} else { \
assign(p_dictionary); \
} \
} \
_FORCE_INLINE_ TypedDictionary() { \
set_typed(m_variant_type, StringName(), Variant(), Variant::OBJECT, T::get_class_static(), Variant()); \
} \
}; \
template <typename T> \
struct GetTypeInfo<TypedDictionary<m_type, T>> { \
static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \
static inline PropertyInfo get_class_info() { \
return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \
vformat("%s;%s", m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type), T::get_class_static())); \
} \
}; \
template <typename T> \
struct GetTypeInfo<const TypedDictionary<m_type, T> &> { \
static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \
static inline PropertyInfo get_class_info() { \
return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \
vformat("%s;%s", m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type), T::get_class_static())); \
} \
};
#define MAKE_TYPED_DICTIONARY_EXPANDED(m_type_key, m_variant_type_key, m_type_value, m_variant_type_value) \
template <> \
class TypedDictionary<m_type_key, m_type_value> : public Dictionary { \
public: \
_FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) { \
ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign an dictionary with a different element type."); \
Dictionary::operator=(p_dictionary); \
} \
_FORCE_INLINE_ TypedDictionary(const Variant &p_variant) : \
TypedDictionary(Dictionary(p_variant)) { \
} \
_FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) { \
set_typed(m_variant_type_key, StringName(), Variant(), m_variant_type_value, StringName(), Variant()); \
if (is_same_typed(p_dictionary)) { \
Dictionary::operator=(p_dictionary); \
} else { \
assign(p_dictionary); \
} \
} \
_FORCE_INLINE_ TypedDictionary() { \
set_typed(m_variant_type_key, StringName(), Variant(), m_variant_type_value, StringName(), Variant()); \
} \
}; \
template <> \
struct GetTypeInfo<TypedDictionary<m_type_key, m_type_value>> { \
static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \
static inline PropertyInfo get_class_info() { \
return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \
vformat("%s;%s", m_variant_type_key == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_key), \
m_variant_type_value == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_value))); \
} \
}; \
template <> \
struct GetTypeInfo<const TypedDictionary<m_type_key, m_type_value> &> { \
static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \
static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \
static inline PropertyInfo get_class_info() { \
return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \
vformat("%s;%s", m_variant_type_key == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_key), \
m_variant_type_value == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_value))); \
} \
};
#define MAKE_TYPED_DICTIONARY_NIL(m_type, m_variant_type) \
MAKE_TYPED_DICTIONARY_WITH_OBJECT(m_type, m_variant_type) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, bool, Variant::BOOL) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint8_t, Variant::INT) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int8_t, Variant::INT) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint16_t, Variant::INT) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int16_t, Variant::INT) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint32_t, Variant::INT) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int32_t, Variant::INT) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint64_t, Variant::INT) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int64_t, Variant::INT) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, float, Variant::FLOAT) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, double, Variant::FLOAT) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, String, Variant::STRING) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector2, Variant::VECTOR2) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector2i, Variant::VECTOR2I) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Rect2, Variant::RECT2) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Rect2i, Variant::RECT2I) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector3, Variant::VECTOR3) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector3i, Variant::VECTOR3I) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Transform2D, Variant::TRANSFORM2D) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Plane, Variant::PLANE) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Quaternion, Variant::QUATERNION) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, AABB, Variant::AABB) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Basis, Variant::BASIS) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Transform3D, Variant::TRANSFORM3D) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Color, Variant::COLOR) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, StringName, Variant::STRING_NAME) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, NodePath, Variant::NODE_PATH) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, RID, Variant::RID) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Callable, Variant::CALLABLE) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Signal, Variant::SIGNAL) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Dictionary, Variant::DICTIONARY) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Array, Variant::ARRAY) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedByteArray, Variant::PACKED_BYTE_ARRAY) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedInt32Array, Variant::PACKED_INT32_ARRAY) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedInt64Array, Variant::PACKED_INT64_ARRAY) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedFloat32Array, Variant::PACKED_FLOAT32_ARRAY) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedFloat64Array, Variant::PACKED_FLOAT64_ARRAY) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedStringArray, Variant::PACKED_STRING_ARRAY) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedVector2Array, Variant::PACKED_VECTOR2_ARRAY) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedColorArray, Variant::PACKED_COLOR_ARRAY) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedVector4Array, Variant::PACKED_VECTOR4_ARRAY) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, IPAddress, Variant::STRING)
#define MAKE_TYPED_DICTIONARY(m_type, m_variant_type) \
MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Variant, Variant::NIL) \
MAKE_TYPED_DICTIONARY_NIL(m_type, m_variant_type)
MAKE_TYPED_DICTIONARY_NIL(Variant, Variant::NIL)
MAKE_TYPED_DICTIONARY(bool, Variant::BOOL)
MAKE_TYPED_DICTIONARY(uint8_t, Variant::INT)
MAKE_TYPED_DICTIONARY(int8_t, Variant::INT)
MAKE_TYPED_DICTIONARY(uint16_t, Variant::INT)
MAKE_TYPED_DICTIONARY(int16_t, Variant::INT)
MAKE_TYPED_DICTIONARY(uint32_t, Variant::INT)
MAKE_TYPED_DICTIONARY(int32_t, Variant::INT)
MAKE_TYPED_DICTIONARY(uint64_t, Variant::INT)
MAKE_TYPED_DICTIONARY(int64_t, Variant::INT)
MAKE_TYPED_DICTIONARY(float, Variant::FLOAT)
MAKE_TYPED_DICTIONARY(double, Variant::FLOAT)
MAKE_TYPED_DICTIONARY(String, Variant::STRING)
MAKE_TYPED_DICTIONARY(Vector2, Variant::VECTOR2)
MAKE_TYPED_DICTIONARY(Vector2i, Variant::VECTOR2I)
MAKE_TYPED_DICTIONARY(Rect2, Variant::RECT2)
MAKE_TYPED_DICTIONARY(Rect2i, Variant::RECT2I)
MAKE_TYPED_DICTIONARY(Vector3, Variant::VECTOR3)
MAKE_TYPED_DICTIONARY(Vector3i, Variant::VECTOR3I)
MAKE_TYPED_DICTIONARY(Transform2D, Variant::TRANSFORM2D)
MAKE_TYPED_DICTIONARY(Plane, Variant::PLANE)
MAKE_TYPED_DICTIONARY(Quaternion, Variant::QUATERNION)
MAKE_TYPED_DICTIONARY(AABB, Variant::AABB)
MAKE_TYPED_DICTIONARY(Basis, Variant::BASIS)
MAKE_TYPED_DICTIONARY(Transform3D, Variant::TRANSFORM3D)
MAKE_TYPED_DICTIONARY(Color, Variant::COLOR)
MAKE_TYPED_DICTIONARY(StringName, Variant::STRING_NAME)
MAKE_TYPED_DICTIONARY(NodePath, Variant::NODE_PATH)
MAKE_TYPED_DICTIONARY(RID, Variant::RID)
MAKE_TYPED_DICTIONARY(Callable, Variant::CALLABLE)
MAKE_TYPED_DICTIONARY(Signal, Variant::SIGNAL)
MAKE_TYPED_DICTIONARY(Dictionary, Variant::DICTIONARY)
MAKE_TYPED_DICTIONARY(Array, Variant::ARRAY)
MAKE_TYPED_DICTIONARY(PackedByteArray, Variant::PACKED_BYTE_ARRAY)
MAKE_TYPED_DICTIONARY(PackedInt32Array, Variant::PACKED_INT32_ARRAY)
MAKE_TYPED_DICTIONARY(PackedInt64Array, Variant::PACKED_INT64_ARRAY)
MAKE_TYPED_DICTIONARY(PackedFloat32Array, Variant::PACKED_FLOAT32_ARRAY)
MAKE_TYPED_DICTIONARY(PackedFloat64Array, Variant::PACKED_FLOAT64_ARRAY)
MAKE_TYPED_DICTIONARY(PackedStringArray, Variant::PACKED_STRING_ARRAY)
MAKE_TYPED_DICTIONARY(PackedVector2Array, Variant::PACKED_VECTOR2_ARRAY)
MAKE_TYPED_DICTIONARY(PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY)
MAKE_TYPED_DICTIONARY(PackedColorArray, Variant::PACKED_COLOR_ARRAY)
MAKE_TYPED_DICTIONARY(PackedVector4Array, Variant::PACKED_VECTOR4_ARRAY)
MAKE_TYPED_DICTIONARY(IPAddress, Variant::STRING)
#undef MAKE_TYPED_DICTIONARY
#undef MAKE_TYPED_DICTIONARY_NIL
#undef MAKE_TYPED_DICTIONARY_EXPANDED
#undef MAKE_TYPED_DICTIONARY_WITH_OBJECT
#endif // TYPED_DICTIONARY_H

View File

@ -2254,6 +2254,7 @@ static void _register_variant_builtin_methods_misc() {
bind_method(Dictionary, size, sarray(), varray());
bind_method(Dictionary, is_empty, sarray(), varray());
bind_method(Dictionary, clear, sarray(), varray());
bind_method(Dictionary, assign, sarray("dictionary"), varray());
bind_method(Dictionary, merge, sarray("dictionary", "overwrite"), varray(false));
bind_method(Dictionary, merged, sarray("dictionary", "overwrite"), varray(false));
bind_method(Dictionary, has, sarray("key"), varray());
@ -2266,6 +2267,18 @@ static void _register_variant_builtin_methods_misc() {
bind_method(Dictionary, duplicate, sarray("deep"), varray(false));
bind_method(Dictionary, get, sarray("key", "default"), varray(Variant()));
bind_method(Dictionary, get_or_add, sarray("key", "default"), varray(Variant()));
bind_method(Dictionary, is_typed, sarray(), varray());
bind_method(Dictionary, is_typed_key, sarray(), varray());
bind_method(Dictionary, is_typed_value, sarray(), varray());
bind_method(Dictionary, is_same_typed, sarray("dictionary"), varray());
bind_method(Dictionary, is_same_typed_key, sarray("dictionary"), varray());
bind_method(Dictionary, is_same_typed_value, sarray("dictionary"), varray());
bind_method(Dictionary, get_typed_key_builtin, sarray(), varray());
bind_method(Dictionary, get_typed_value_builtin, sarray(), varray());
bind_method(Dictionary, get_typed_key_class_name, sarray(), varray());
bind_method(Dictionary, get_typed_value_class_name, sarray(), varray());
bind_method(Dictionary, get_typed_key_script, sarray(), varray());
bind_method(Dictionary, get_typed_value_script, sarray(), varray());
bind_method(Dictionary, make_read_only, sarray(), varray());
bind_method(Dictionary, is_read_only, sarray(), varray());
bind_method(Dictionary, recursive_equal, sarray("dictionary", "recursion_count"), varray());

View File

@ -198,6 +198,7 @@ void Variant::_register_variant_constructors() {
add_constructor<VariantConstructNoArgs<Dictionary>>(sarray());
add_constructor<VariantConstructor<Dictionary, Dictionary>>(sarray("from"));
add_constructor<VariantConstructorTypedDictionary>(sarray("base", "key_type", "key_class_name", "key_script", "value_type", "value_class_name", "value_script"));
add_constructor<VariantConstructNoArgs<Array>>(sarray());
add_constructor<VariantConstructor<Array, Array>>(sarray("from"));

View File

@ -400,6 +400,112 @@ public:
}
};
class VariantConstructorTypedDictionary {
public:
static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) {
if (p_args[0]->get_type() != Variant::DICTIONARY) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 0;
r_error.expected = Variant::DICTIONARY;
return;
}
if (p_args[1]->get_type() != Variant::INT) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 1;
r_error.expected = Variant::INT;
return;
}
if (p_args[2]->get_type() != Variant::STRING_NAME) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 2;
r_error.expected = Variant::STRING_NAME;
return;
}
if (p_args[4]->get_type() != Variant::INT) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 4;
r_error.expected = Variant::INT;
return;
}
if (p_args[5]->get_type() != Variant::STRING_NAME) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
r_error.argument = 5;
r_error.expected = Variant::STRING_NAME;
return;
}
const Dictionary &base_dict = *VariantGetInternalPtr<Dictionary>::get_ptr(p_args[0]);
const uint32_t key_type = p_args[1]->operator uint32_t();
const StringName &key_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[2]);
const uint32_t value_type = p_args[4]->operator uint32_t();
const StringName &value_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[5]);
r_ret = Dictionary(base_dict, key_type, key_class_name, *p_args[3], value_type, value_class_name, *p_args[6]);
}
static inline void validated_construct(Variant *r_ret, const Variant **p_args) {
const Dictionary &base_dict = *VariantGetInternalPtr<Dictionary>::get_ptr(p_args[0]);
const uint32_t key_type = p_args[1]->operator uint32_t();
const StringName &key_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[2]);
const uint32_t value_type = p_args[4]->operator uint32_t();
const StringName &value_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[5]);
*r_ret = Dictionary(base_dict, key_type, key_class_name, *p_args[3], value_type, value_class_name, *p_args[6]);
}
static void ptr_construct(void *base, const void **p_args) {
const Dictionary &base_dict = PtrToArg<Dictionary>::convert(p_args[0]);
const uint32_t key_type = PtrToArg<uint32_t>::convert(p_args[1]);
const StringName &key_class_name = PtrToArg<StringName>::convert(p_args[2]);
const Variant &key_script = PtrToArg<Variant>::convert(p_args[3]);
const uint32_t value_type = PtrToArg<uint32_t>::convert(p_args[4]);
const StringName &value_class_name = PtrToArg<StringName>::convert(p_args[5]);
const Variant &value_script = PtrToArg<Variant>::convert(p_args[6]);
Dictionary dst_arr = Dictionary(base_dict, key_type, key_class_name, key_script, value_type, value_class_name, value_script);
PtrConstruct<Dictionary>::construct(dst_arr, base);
}
static int get_argument_count() {
return 7;
}
static Variant::Type get_argument_type(int p_arg) {
switch (p_arg) {
case 0: {
return Variant::DICTIONARY;
} break;
case 1: {
return Variant::INT;
} break;
case 2: {
return Variant::STRING_NAME;
} break;
case 3: {
return Variant::NIL;
} break;
case 4: {
return Variant::INT;
} break;
case 5: {
return Variant::STRING_NAME;
} break;
case 6: {
return Variant::NIL;
} break;
default: {
return Variant::NIL;
} break;
}
}
static Variant::Type get_base_type() {
return Variant::DICTIONARY;
}
};
class VariantConstructorTypedArray {
public:
static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) {

View File

@ -1140,6 +1140,146 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream,
return ERR_PARSE_ERROR;
}
}
} else if (id == "Dictionary") {
Error err = OK;
get_token(p_stream, token, line, r_err_str);
if (token.type != TK_BRACKET_OPEN) {
r_err_str = "Expected '['";
return ERR_PARSE_ERROR;
}
get_token(p_stream, token, line, r_err_str);
if (token.type != TK_IDENTIFIER) {
r_err_str = "Expected type identifier for key";
return ERR_PARSE_ERROR;
}
static HashMap<StringName, Variant::Type> builtin_types;
if (builtin_types.is_empty()) {
for (int i = 1; i < Variant::VARIANT_MAX; i++) {
builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i;
}
}
Dictionary dict;
Variant::Type key_type = Variant::NIL;
StringName key_class_name;
Variant key_script;
bool got_comma_token = false;
if (builtin_types.has(token.value)) {
key_type = builtin_types.get(token.value);
} else if (token.value == "Resource" || token.value == "SubResource" || token.value == "ExtResource") {
Variant resource;
err = parse_value(token, resource, p_stream, line, r_err_str, p_res_parser);
if (err) {
if (token.value == "Resource" && err == ERR_PARSE_ERROR && r_err_str == "Expected '('" && token.type == TK_COMMA) {
err = OK;
r_err_str = String();
key_type = Variant::OBJECT;
key_class_name = token.value;
got_comma_token = true;
} else {
return err;
}
} else {
Ref<Script> script = resource;
if (script.is_valid() && script->is_valid()) {
key_type = Variant::OBJECT;
key_class_name = script->get_instance_base_type();
key_script = script;
}
}
} else if (ClassDB::class_exists(token.value)) {
key_type = Variant::OBJECT;
key_class_name = token.value;
}
if (!got_comma_token) {
get_token(p_stream, token, line, r_err_str);
if (token.type != TK_COMMA) {
r_err_str = "Expected ',' after key type";
return ERR_PARSE_ERROR;
}
}
get_token(p_stream, token, line, r_err_str);
if (token.type != TK_IDENTIFIER) {
r_err_str = "Expected type identifier for value";
return ERR_PARSE_ERROR;
}
Variant::Type value_type = Variant::NIL;
StringName value_class_name;
Variant value_script;
bool got_bracket_token = false;
if (builtin_types.has(token.value)) {
value_type = builtin_types.get(token.value);
} else if (token.value == "Resource" || token.value == "SubResource" || token.value == "ExtResource") {
Variant resource;
err = parse_value(token, resource, p_stream, line, r_err_str, p_res_parser);
if (err) {
if (token.value == "Resource" && err == ERR_PARSE_ERROR && r_err_str == "Expected '('" && token.type == TK_BRACKET_CLOSE) {
err = OK;
r_err_str = String();
value_type = Variant::OBJECT;
value_class_name = token.value;
got_comma_token = true;
} else {
return err;
}
} else {
Ref<Script> script = resource;
if (script.is_valid() && script->is_valid()) {
value_type = Variant::OBJECT;
value_class_name = script->get_instance_base_type();
value_script = script;
}
}
} else if (ClassDB::class_exists(token.value)) {
value_type = Variant::OBJECT;
value_class_name = token.value;
}
if (key_type != Variant::NIL || value_type != Variant::NIL) {
dict.set_typed(key_type, key_class_name, key_script, value_type, value_class_name, value_script);
}
if (!got_bracket_token) {
get_token(p_stream, token, line, r_err_str);
if (token.type != TK_BRACKET_CLOSE) {
r_err_str = "Expected ']'";
return ERR_PARSE_ERROR;
}
}
get_token(p_stream, token, line, r_err_str);
if (token.type != TK_PARENTHESIS_OPEN) {
r_err_str = "Expected '('";
return ERR_PARSE_ERROR;
}
get_token(p_stream, token, line, r_err_str);
if (token.type != TK_CURLY_BRACKET_OPEN) {
r_err_str = "Expected '{'";
return ERR_PARSE_ERROR;
}
Dictionary values;
err = _parse_dictionary(values, p_stream, line, r_err_str, p_res_parser);
if (err) {
return err;
}
get_token(p_stream, token, line, r_err_str);
if (token.type != TK_PARENTHESIS_CLOSE) {
r_err_str = "Expected ')'";
return ERR_PARSE_ERROR;
}
dict.assign(values);
value = dict;
} else if (id == "Array") {
Error err = OK;
@ -2036,22 +2176,85 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
case Variant::DICTIONARY: {
Dictionary dict = p_variant;
if (dict.is_typed()) {
p_store_string_func(p_store_string_ud, "Dictionary[");
Variant::Type key_builtin_type = (Variant::Type)dict.get_typed_key_builtin();
StringName key_class_name = dict.get_typed_key_class_name();
Ref<Script> key_script = dict.get_typed_key_script();
if (key_script.is_valid()) {
String resource_text;
if (p_encode_res_func) {
resource_text = p_encode_res_func(p_encode_res_ud, key_script);
}
if (resource_text.is_empty() && key_script->get_path().is_resource_file()) {
resource_text = "Resource(\"" + key_script->get_path() + "\")";
}
if (!resource_text.is_empty()) {
p_store_string_func(p_store_string_ud, resource_text);
} else {
ERR_PRINT("Failed to encode a path to a custom script for a dictionary key type.");
p_store_string_func(p_store_string_ud, key_class_name);
}
} else if (key_class_name != StringName()) {
p_store_string_func(p_store_string_ud, key_class_name);
} else if (key_builtin_type == Variant::NIL) {
p_store_string_func(p_store_string_ud, "Variant");
} else {
p_store_string_func(p_store_string_ud, Variant::get_type_name(key_builtin_type));
}
p_store_string_func(p_store_string_ud, ", ");
Variant::Type value_builtin_type = (Variant::Type)dict.get_typed_value_builtin();
StringName value_class_name = dict.get_typed_value_class_name();
Ref<Script> value_script = dict.get_typed_value_script();
if (value_script.is_valid()) {
String resource_text;
if (p_encode_res_func) {
resource_text = p_encode_res_func(p_encode_res_ud, value_script);
}
if (resource_text.is_empty() && value_script->get_path().is_resource_file()) {
resource_text = "Resource(\"" + value_script->get_path() + "\")";
}
if (!resource_text.is_empty()) {
p_store_string_func(p_store_string_ud, resource_text);
} else {
ERR_PRINT("Failed to encode a path to a custom script for a dictionary value type.");
p_store_string_func(p_store_string_ud, value_class_name);
}
} else if (value_class_name != StringName()) {
p_store_string_func(p_store_string_ud, value_class_name);
} else if (value_builtin_type == Variant::NIL) {
p_store_string_func(p_store_string_ud, "Variant");
} else {
p_store_string_func(p_store_string_ud, Variant::get_type_name(value_builtin_type));
}
p_store_string_func(p_store_string_ud, "](");
}
if (unlikely(p_recursion_count > MAX_RECURSION)) {
ERR_PRINT("Max recursion reached");
p_store_string_func(p_store_string_ud, "{}");
} else {
p_recursion_count++;
List<Variant> keys;
dict.get_key_list(&keys);
keys.sort();
if (keys.is_empty()) { // Avoid unnecessary line break.
if (keys.is_empty()) {
// Avoid unnecessary line break.
p_store_string_func(p_store_string_ud, "{}");
break;
}
} else {
p_recursion_count++;
p_store_string_func(p_store_string_ud, "{\n");
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
write(E->get(), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat);
p_store_string_func(p_store_string_ud, ": ");
@ -2065,11 +2268,17 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
p_store_string_func(p_store_string_ud, "}");
}
}
if (dict.is_typed()) {
p_store_string_func(p_store_string_ud, ")");
}
} break;
case Variant::ARRAY: {
Array array = p_variant;
if (array.get_typed_builtin() != Variant::NIL) {
if (array.is_typed()) {
p_store_string_func(p_store_string_ud, "Array[");
Variant::Type builtin_type = (Variant::Type)array.get_typed_builtin();
@ -2107,6 +2316,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
p_recursion_count++;
p_store_string_func(p_store_string_ud, "[");
bool first = true;
for (const Variant &var : array) {
if (first) {
@ -2120,7 +2330,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
p_store_string_func(p_store_string_ud, "]");
}
if (array.get_typed_builtin() != Variant::NIL) {
if (array.is_typed()) {
p_store_string_func(p_store_string_ud, ")");
}
} break;

View File

@ -703,6 +703,50 @@ struct VariantIndexedSetGet_Array {
static uint64_t get_indexed_size(const Variant *base) { return 0; }
};
struct VariantIndexedSetGet_Dictionary {
static void get(const Variant *base, int64_t index, Variant *value, bool *oob) {
const Variant *ptr = VariantGetInternalPtr<Dictionary>::get_ptr(base)->getptr(index);
if (!ptr) {
*oob = true;
return;
}
*value = *ptr;
*oob = false;
}
static void ptr_get(const void *base, int64_t index, void *member) {
// Avoid ptrconvert for performance.
const Dictionary &v = *reinterpret_cast<const Dictionary *>(base);
const Variant *ptr = v.getptr(index);
NULL_TEST(ptr);
PtrToArg<Variant>::encode(*ptr, member);
}
static void set(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) {
if (VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only()) {
*valid = false;
*oob = true;
return;
}
(*VariantGetInternalPtr<Dictionary>::get_ptr(base))[index] = *value;
*oob = false;
*valid = true;
}
static void validated_set(Variant *base, int64_t index, const Variant *value, bool *oob) {
if (VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only()) {
*oob = true;
return;
}
(*VariantGetInternalPtr<Dictionary>::get_ptr(base))[index] = *value;
*oob = false;
}
static void ptr_set(void *base, int64_t index, const void *member) {
Dictionary &v = *reinterpret_cast<Dictionary *>(base);
v[index] = PtrToArg<Variant>::convert(member);
}
static Variant::Type get_index_type() { return Variant::NIL; }
static uint32_t get_index_usage() { return PROPERTY_USAGE_DEFAULT; }
static uint64_t get_indexed_size(const Variant *base) { return VariantGetInternalPtr<Dictionary>::get_ptr(base)->size(); }
};
struct VariantIndexedSetGet_String {
static void get(const Variant *base, int64_t index, Variant *value, bool *oob) {
int64_t length = VariantGetInternalPtr<String>::get_ptr(base)->length();
@ -789,51 +833,6 @@ struct VariantIndexedSetGet_String {
static uint64_t get_indexed_size(const Variant *base) { return VariantInternal::get_string(base)->length(); }
};
#define INDEXED_SETGET_STRUCT_DICT(m_base_type) \
struct VariantIndexedSetGet_##m_base_type { \
static void get(const Variant *base, int64_t index, Variant *value, bool *oob) { \
const Variant *ptr = VariantGetInternalPtr<m_base_type>::get_ptr(base)->getptr(index); \
if (!ptr) { \
*oob = true; \
return; \
} \
*value = *ptr; \
*oob = false; \
} \
static void ptr_get(const void *base, int64_t index, void *member) { \
/* avoid ptrconvert for performance*/ \
const m_base_type &v = *reinterpret_cast<const m_base_type *>(base); \
const Variant *ptr = v.getptr(index); \
NULL_TEST(ptr); \
PtrToArg<Variant>::encode(*ptr, member); \
} \
static void set(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) { \
if (VariantGetInternalPtr<m_base_type>::get_ptr(base)->is_read_only()) { \
*valid = false; \
*oob = true; \
return; \
} \
(*VariantGetInternalPtr<m_base_type>::get_ptr(base))[index] = *value; \
*oob = false; \
*valid = true; \
} \
static void validated_set(Variant *base, int64_t index, const Variant *value, bool *oob) { \
if (VariantGetInternalPtr<m_base_type>::get_ptr(base)->is_read_only()) { \
*oob = true; \
return; \
} \
(*VariantGetInternalPtr<m_base_type>::get_ptr(base))[index] = *value; \
*oob = false; \
} \
static void ptr_set(void *base, int64_t index, const void *member) { \
m_base_type &v = *reinterpret_cast<m_base_type *>(base); \
v[index] = PtrToArg<Variant>::convert(member); \
} \
static Variant::Type get_index_type() { return Variant::NIL; } \
static uint32_t get_index_usage() { return PROPERTY_USAGE_DEFAULT; } \
static uint64_t get_indexed_size(const Variant *base) { return VariantGetInternalPtr<m_base_type>::get_ptr(base)->size(); } \
};
INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector2, double, real_t, 2)
INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector2i, int64_t, int32_t, 2)
INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector3, double, real_t, 3)
@ -858,8 +857,6 @@ INDEXED_SETGET_STRUCT_TYPED(PackedStringArray, String)
INDEXED_SETGET_STRUCT_TYPED(PackedColorArray, Color)
INDEXED_SETGET_STRUCT_TYPED(PackedVector4Array, Vector4)
INDEXED_SETGET_STRUCT_DICT(Dictionary)
struct VariantIndexedSetterGetterInfo {
void (*setter)(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) = nullptr;
void (*getter)(const Variant *base, int64_t index, Variant *value, bool *oob) = nullptr;

View File

@ -2915,6 +2915,9 @@
<constant name="PROPERTY_HINT_ARRAY_TYPE" value="31" enum="PropertyHint">
Hints that a property is an [Array] with the stored type specified in the hint string.
</constant>
<constant name="PROPERTY_HINT_DICTIONARY_TYPE" value="38" enum="PropertyHint">
Hints that a property is a [Dictionary] with the stored types specified in the hint string.
</constant>
<constant name="PROPERTY_HINT_LOCALE_ID" value="32" enum="PropertyHint">
Hints that a string property is a locale code. Editing it will show a locale dialog for picking language and country.
</constant>
@ -2930,7 +2933,7 @@
<constant name="PROPERTY_HINT_PASSWORD" value="36" enum="PropertyHint">
Hints that a string property is a password, and every character is replaced with the secret character.
</constant>
<constant name="PROPERTY_HINT_MAX" value="38" enum="PropertyHint">
<constant name="PROPERTY_HINT_MAX" value="39" enum="PropertyHint">
Represents the size of the [enum PropertyHint] enum.
</constant>
<constant name="PROPERTY_USAGE_NONE" value="0" enum="PropertyUsageFlags" is_bitfield="true">

View File

@ -148,6 +148,19 @@
Constructs an empty [Dictionary].
</description>
</constructor>
<constructor name="Dictionary">
<return type="Dictionary" />
<param index="0" name="base" type="Dictionary" />
<param index="1" name="key_type" type="int" />
<param index="2" name="key_class_name" type="StringName" />
<param index="3" name="key_script" type="Variant" />
<param index="4" name="value_type" type="int" />
<param index="5" name="value_class_name" type="StringName" />
<param index="6" name="value_script" type="Variant" />
<description>
Creates a typed dictionary from the [param base] dictionary. A typed dictionary can only contain keys and values of the given types, or that inherit from the given classes, as described by this constructor's parameters.
</description>
</constructor>
<constructor name="Dictionary">
<return type="Dictionary" />
<param index="0" name="from" type="Dictionary" />
@ -157,6 +170,13 @@
</constructor>
</constructors>
<methods>
<method name="assign">
<return type="void" />
<param index="0" name="dictionary" type="Dictionary" />
<description>
Assigns elements of another [param dictionary] into the dictionary. Resizes the dictionary to match [param dictionary]. Performs type conversions if the dictionary is typed.
</description>
</method>
<method name="clear">
<return type="void" />
<description>
@ -202,6 +222,42 @@
Gets a value and ensures the key is set. If the [param key] exists in the dictionary, this behaves like [method get]. Otherwise, the [param default] value is inserted into the dictionary and returned.
</description>
</method>
<method name="get_typed_key_builtin" qualifiers="const">
<return type="int" />
<description>
Returns the built-in [Variant] type of the typed dictionary's keys as a [enum Variant.Type] constant. If the keys are not typed, returns [constant TYPE_NIL]. See also [method is_typed_key].
</description>
</method>
<method name="get_typed_key_class_name" qualifiers="const">
<return type="StringName" />
<description>
Returns the [b]built-in[/b] class name of the typed dictionary's keys, if the built-in [Variant] type is [constant TYPE_OBJECT]. Otherwise, returns an empty [StringName]. See also [method is_typed_key] and [method Object.get_class].
</description>
</method>
<method name="get_typed_key_script" qualifiers="const">
<return type="Variant" />
<description>
Returns the [Script] instance associated with this typed dictionary's keys, or [code]null[/code] if it does not exist. See also [method is_typed_key].
</description>
</method>
<method name="get_typed_value_builtin" qualifiers="const">
<return type="int" />
<description>
Returns the built-in [Variant] type of the typed dictionary's values as a [enum Variant.Type] constant. If the values are not typed, returns [constant TYPE_NIL]. See also [method is_typed_value].
</description>
</method>
<method name="get_typed_value_class_name" qualifiers="const">
<return type="StringName" />
<description>
Returns the [b]built-in[/b] class name of the typed dictionary's values, if the built-in [Variant] type is [constant TYPE_OBJECT]. Otherwise, returns an empty [StringName]. See also [method is_typed_value] and [method Object.get_class].
</description>
</method>
<method name="get_typed_value_script" qualifiers="const">
<return type="Variant" />
<description>
Returns the [Script] instance associated with this typed dictionary's values, or [code]null[/code] if it does not exist. See also [method is_typed_value].
</description>
</method>
<method name="has" qualifiers="const">
<return type="bool" />
<param index="0" name="key" type="Variant" />
@ -284,6 +340,45 @@
Returns [code]true[/code] if the dictionary is read-only. See [method make_read_only]. Dictionaries are automatically read-only if declared with [code]const[/code] keyword.
</description>
</method>
<method name="is_same_typed" qualifiers="const">
<return type="bool" />
<param index="0" name="dictionary" type="Dictionary" />
<description>
Returns [code]true[/code] if the dictionary is typed the same as [param dictionary].
</description>
</method>
<method name="is_same_typed_key" qualifiers="const">
<return type="bool" />
<param index="0" name="dictionary" type="Dictionary" />
<description>
Returns [code]true[/code] if the dictionary's keys are typed the same as [param dictionary]'s keys.
</description>
</method>
<method name="is_same_typed_value" qualifiers="const">
<return type="bool" />
<param index="0" name="dictionary" type="Dictionary" />
<description>
Returns [code]true[/code] if the dictionary's values are typed the same as [param dictionary]'s values.
</description>
</method>
<method name="is_typed" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if the dictionary is typed. Typed dictionaries can only store keys/values of their associated type and provide type safety for the [code][][/code] operator. Methods of typed dictionary still return [Variant].
</description>
</method>
<method name="is_typed_key" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if the dictionary's keys are typed.
</description>
</method>
<method name="is_typed_value" qualifiers="const">
<return type="bool" />
<description>
Returns [code]true[/code] if the dictionary's values are typed.
</description>
</method>
<method name="keys" qualifiers="const">
<return type="Array" />
<description>

View File

@ -1507,24 +1507,23 @@ def make_type(klass: str, state: State) -> str:
if klass.find("*") != -1: # Pointer, ignore
return f"``{klass}``"
link_type = klass
is_array = False
if link_type.endswith("[]"): # Typed array, strip [] to link to contained type.
link_type = link_type[:-2]
is_array = True
def resolve_type(link_type: str) -> str:
if link_type in state.classes:
type_rst = f":ref:`{link_type}<class_{link_type}>`"
if is_array:
type_rst = f":ref:`Array<class_Array>`\\[{type_rst}\\]"
return type_rst
return f":ref:`{link_type}<class_{link_type}>`"
else:
print_error(f'{state.current_class}.xml: Unresolved type "{link_type}".', state)
type_rst = f"``{link_type}``"
if is_array:
type_rst = f":ref:`Array<class_Array>`\\[{type_rst}\\]"
return type_rst
return f"``{link_type}``"
if klass.endswith("[]"): # Typed array, strip [] to link to contained type.
return f":ref:`Array<class_Array>`\\[{resolve_type(klass[:-len('[]')])}\\]"
if klass.startswith("Dictionary["): # Typed dictionary, split elements to link contained types.
parts = klass[len("Dictionary[") : -len("]")].partition(", ")
key = parts[0]
value = parts[2]
return f":ref:`Dictionary<class_Dictionary>`\\[{resolve_type(key)}, {resolve_type(value)}\\]"
return resolve_type(klass)
def make_enum(t: str, is_bitfield: bool, state: State) -> str:

View File

@ -576,6 +576,22 @@ String ConnectDialog::get_signature(const MethodInfo &p_method, PackedStringArra
type_name = "Array";
}
break;
case Variant::DICTIONARY:
type_name = "Dictionary";
if (pi.hint == PROPERTY_HINT_DICTIONARY_TYPE && !pi.hint_string.is_empty()) {
String key_hint = pi.hint_string.get_slice(";", 0);
String value_hint = pi.hint_string.get_slice(";", 1);
if (key_hint.is_empty() || key_hint.begins_with("res://")) {
key_hint = "Variant";
}
if (value_hint.is_empty() || value_hint.begins_with("res://")) {
value_hint = "Variant";
}
if (key_hint != "Variant" || value_hint != "Variant") {
type_name += "[" + key_hint + ", " + value_hint + "]";
}
}
break;
case Variant::OBJECT:
if (pi.class_name != StringName()) {
type_name = pi.class_name;

View File

@ -571,6 +571,8 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) {
prop.type = retinfo.class_name;
} else if (retinfo.type == Variant::ARRAY && retinfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
prop.type = retinfo.hint_string + "[]";
} else if (retinfo.type == Variant::DICTIONARY && retinfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
prop.type = "Dictionary[" + retinfo.hint_string.replace(";", ", ") + "]";
} else if (retinfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
prop.type = retinfo.hint_string;
} else if (retinfo.type == Variant::NIL && retinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {

View File

@ -376,10 +376,10 @@ static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_i
}
p_rt->push_color(type_color);
bool add_array = false;
bool add_typed_container = false;
if (can_ref) {
if (link_t.ends_with("[]")) {
add_array = true;
add_typed_container = true;
link_t = link_t.trim_suffix("[]");
display_t = display_t.trim_suffix("[]");
@ -387,6 +387,22 @@ static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_i
p_rt->add_text("Array");
p_rt->pop(); // meta
p_rt->add_text("[");
} else if (link_t.begins_with("Dictionary[")) {
add_typed_container = true;
link_t = link_t.trim_prefix("Dictionary[").trim_suffix("]");
display_t = display_t.trim_prefix("Dictionary[").trim_suffix("]");
p_rt->push_meta("#Dictionary", RichTextLabel::META_UNDERLINE_ON_HOVER); // class
p_rt->add_text("Dictionary");
p_rt->pop(); // meta
p_rt->add_text("[");
p_rt->push_meta("#" + link_t.get_slice(", ", 0), RichTextLabel::META_UNDERLINE_ON_HOVER); // class
p_rt->add_text(_contextualize_class_specifier(display_t.get_slice(", ", 0), p_class));
p_rt->pop(); // meta
p_rt->add_text(", ");
link_t = link_t.get_slice(", ", 1);
display_t = _contextualize_class_specifier(display_t.get_slice(", ", 1), p_class);
} else if (is_bitfield) {
p_rt->push_color(Color(type_color, 0.5));
p_rt->push_hint(TTR("This value is an integer composed as a bitmask of the following flags."));
@ -405,7 +421,7 @@ static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_i
p_rt->add_text(display_t);
if (can_ref) {
p_rt->pop(); // meta
if (add_array) {
if (add_typed_container) {
p_rt->add_text("]");
} else if (is_bitfield) {
p_rt->push_color(Color(type_color, 0.5));

View File

@ -3771,7 +3771,7 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
return editor;
} else {
EditorPropertyDictionary *editor = memnew(EditorPropertyDictionary);
editor->setup(p_hint);
editor->setup(p_hint, p_hint_text);
return editor;
}
} break;

View File

@ -863,6 +863,26 @@ EditorPropertyArray::EditorPropertyArray() {
///////////////////// DICTIONARY ///////////////////////////
void EditorPropertyDictionary::initialize_dictionary(Variant &p_dictionary) {
if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {
Dictionary dict;
StringName key_subtype_class;
Ref<Script> key_subtype_script;
if (key_subtype == Variant::OBJECT && !key_subtype_hint_string.is_empty() && ClassDB::class_exists(key_subtype_hint_string)) {
key_subtype_class = key_subtype_hint_string;
}
StringName value_subtype_class;
Ref<Script> value_subtype_script;
if (value_subtype == Variant::OBJECT && !value_subtype_hint_string.is_empty() && ClassDB::class_exists(value_subtype_hint_string)) {
value_subtype_class = value_subtype_hint_string;
}
dict.set_typed(key_subtype, key_subtype_class, key_subtype_script, value_subtype, value_subtype_class, value_subtype_script);
p_dictionary = dict;
} else {
VariantInternal::initialize(&p_dictionary, Variant::DICTIONARY);
}
}
void EditorPropertyDictionary::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
if (p_value.get_type() == Variant::OBJECT && p_value.is_null()) {
p_value = Variant(); // `EditorResourcePicker` resets to `Ref<Resource>()`. See GH-82716.
@ -914,16 +934,29 @@ void EditorPropertyDictionary::_create_new_property_slot(int p_idx) {
EditorProperty *prop = memnew(EditorPropertyNil);
hbox->add_child(prop);
bool use_key = p_idx == EditorPropertyDictionaryObject::NEW_KEY_INDEX;
bool is_untyped_dict = (use_key ? key_subtype : value_subtype) == Variant::NIL;
if (is_untyped_dict) {
Button *edit_btn = memnew(Button);
edit_btn->set_icon(get_editor_theme_icon(SNAME("Edit")));
edit_btn->set_disabled(is_read_only());
edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_change_type).bind(edit_btn, slots.size()));
hbox->add_child(edit_btn);
} else if (p_idx >= 0) {
Button *remove_btn = memnew(Button);
remove_btn->set_icon(get_editor_theme_icon(SNAME("Remove")));
remove_btn->set_disabled(is_read_only());
remove_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_remove_pressed).bind(slots.size()));
hbox->add_child(remove_btn);
}
if (add_panel) {
add_panel->get_child(0)->add_child(hbox);
} else {
property_vbox->add_child(hbox);
}
Slot slot;
slot.prop = prop;
slot.object = object;
@ -969,15 +1002,70 @@ void EditorPropertyDictionary::_change_type_menu(int p_index) {
}
}
void EditorPropertyDictionary::setup(PropertyHint p_hint) {
property_hint = p_hint;
void EditorPropertyDictionary::setup(PropertyHint p_hint, const String &p_hint_string) {
PackedStringArray types = p_hint_string.split(";");
if (types.size() > 0 && !types[0].is_empty()) {
String key = types[0];
int hint_key_subtype_separator = key.find(":");
if (hint_key_subtype_separator >= 0) {
String key_subtype_string = key.substr(0, hint_key_subtype_separator);
int slash_pos = key_subtype_string.find("/");
if (slash_pos >= 0) {
key_subtype_hint = PropertyHint(key_subtype_string.substr(slash_pos + 1, key_subtype_string.size() - slash_pos - 1).to_int());
key_subtype_string = key_subtype_string.substr(0, slash_pos);
}
key_subtype_hint_string = key.substr(hint_key_subtype_separator + 1, key.size() - hint_key_subtype_separator - 1);
key_subtype = Variant::Type(key_subtype_string.to_int());
Variant new_key = object->get_new_item_key();
VariantInternal::initialize(&new_key, key_subtype);
object->set_new_item_key(new_key);
}
}
if (types.size() > 1 && !types[1].is_empty()) {
String value = types[1];
int hint_value_subtype_separator = value.find(":");
if (hint_value_subtype_separator >= 0) {
String value_subtype_string = value.substr(0, hint_value_subtype_separator);
int slash_pos = value_subtype_string.find("/");
if (slash_pos >= 0) {
value_subtype_hint = PropertyHint(value_subtype_string.substr(slash_pos + 1, value_subtype_string.size() - slash_pos - 1).to_int());
value_subtype_string = value_subtype_string.substr(0, slash_pos);
}
value_subtype_hint_string = value.substr(hint_value_subtype_separator + 1, value.size() - hint_value_subtype_separator - 1);
value_subtype = Variant::Type(value_subtype_string.to_int());
Variant new_value = object->get_new_item_value();
VariantInternal::initialize(&new_value, value_subtype);
object->set_new_item_value(new_value);
}
}
}
void EditorPropertyDictionary::update_property() {
Variant updated_val = get_edited_property_value();
String dict_type_name = "Dictionary";
if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) {
String key_subtype_name = "Variant";
if (key_subtype == Variant::OBJECT && (key_subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || key_subtype_hint == PROPERTY_HINT_NODE_TYPE)) {
key_subtype_name = key_subtype_hint_string;
} else if (key_subtype != Variant::NIL) {
key_subtype_name = Variant::get_type_name(key_subtype);
}
String value_subtype_name = "Variant";
if (value_subtype == Variant::OBJECT && (value_subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || value_subtype_hint == PROPERTY_HINT_NODE_TYPE)) {
value_subtype_name = value_subtype_hint_string;
} else if (value_subtype != Variant::NIL) {
value_subtype_name = Variant::get_type_name(value_subtype);
}
dict_type_name += vformat("[%s, %s]", key_subtype_name, value_subtype_name);
}
if (updated_val.get_type() != Variant::DICTIONARY) {
edit->set_text(TTR("Dictionary (Nil)")); // This provides symmetry with the array property.
edit->set_text(vformat(TTR("(Nil) %s"), dict_type_name)); // This provides symmetry with the array property.
edit->set_pressed(false);
if (container) {
set_bottom_editor(nullptr);
@ -993,7 +1081,7 @@ void EditorPropertyDictionary::update_property() {
Dictionary dict = updated_val;
object->set_dict(updated_val);
edit->set_text(vformat(TTR("Dictionary (size %d)"), dict.size()));
edit->set_text(vformat(TTR("%s (size %d)"), dict_type_name, dict.size()));
bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property());
if (edit->is_pressed() != unfolded) {
@ -1074,7 +1162,9 @@ void EditorPropertyDictionary::update_property() {
editor->setup("Object");
new_prop = editor;
} else {
new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE);
bool use_key = slot.index == EditorPropertyDictionaryObject::NEW_KEY_INDEX;
new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", use_key ? key_subtype_hint : value_subtype_hint,
use_key ? key_subtype_hint_string : value_subtype_hint_string, PROPERTY_USAGE_NONE);
}
new_prop->set_selectable(false);
new_prop->set_use_folding(is_using_folding());
@ -1108,6 +1198,13 @@ void EditorPropertyDictionary::update_property() {
}
}
void EditorPropertyDictionary::_remove_pressed(int p_slot_index) {
Dictionary dict = object->get_dict().duplicate();
dict.erase(dict.get_key_at_index(p_slot_index));
emit_changed(get_edited_property(), dict);
}
void EditorPropertyDictionary::_object_id_selected(const StringName &p_property, ObjectID p_id) {
emit_signal(SNAME("object_id_selected"), p_property, p_id);
}
@ -1140,7 +1237,7 @@ void EditorPropertyDictionary::_notification(int p_what) {
void EditorPropertyDictionary::_edit_pressed() {
Variant prop_val = get_edited_property_value();
if (prop_val.get_type() == Variant::NIL && edit->is_pressed()) {
VariantInternal::initialize(&prop_val, Variant::DICTIONARY);
initialize_dictionary(prop_val);
emit_changed(get_edited_property(), prop_val);
}
@ -1187,6 +1284,14 @@ EditorPropertyDictionary::EditorPropertyDictionary() {
change_type->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyDictionary::_change_type_menu));
changing_type_index = -1;
has_borders = true;
key_subtype = Variant::NIL;
key_subtype_hint = PROPERTY_HINT_NONE;
key_subtype_hint_string = "";
value_subtype = Variant::NIL;
value_subtype_hint = PROPERTY_HINT_NONE;
value_subtype_hint_string = "";
}
///////////////////// LOCALIZABLE STRING ///////////////////////////

View File

@ -219,7 +219,6 @@ class EditorPropertyDictionary : public EditorProperty {
EditorSpinSlider *size_sliderv = nullptr;
Button *button_add_item = nullptr;
EditorPaginator *paginator = nullptr;
PropertyHint property_hint;
LocalVector<Slot> slots;
void _create_new_property_slot(int p_idx);
@ -231,12 +230,21 @@ class EditorPropertyDictionary : public EditorProperty {
void _add_key_value();
void _object_id_selected(const StringName &p_property, ObjectID p_id);
void _remove_pressed(int p_slot_index);
Variant::Type key_subtype;
PropertyHint key_subtype_hint;
String key_subtype_hint_string;
Variant::Type value_subtype;
PropertyHint value_subtype_hint;
String value_subtype_hint_string;
void initialize_dictionary(Variant &p_dictionary);
protected:
void _notification(int p_what);
public:
void setup(PropertyHint p_hint);
void setup(PropertyHint p_hint, const String &p_hint_string = "");
virtual void update_property() override;
virtual bool is_colored(ColorationMode p_mode) override;
EditorPropertyDictionary();

View File

@ -84,6 +84,15 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type
return;
}
}
if (p_gdtype.builtin_type == Variant::DICTIONARY && p_gdtype.has_container_element_types()) {
String key, value;
_doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(0), key, r_enum);
_doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(1), value, r_enum);
if (key != "Variant" || value != "Variant") {
r_type = "Dictionary[" + key + ", " + value + "]";
return;
}
}
r_type = Variant::get_type_name(p_gdtype.builtin_type);
return;
case GDType::NATIVE:
@ -155,34 +164,82 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
return "<Object>";
case Variant::DICTIONARY: {
const Dictionary dict = p_variant;
String result;
if (dict.is_typed()) {
result += "Dictionary[";
Ref<Script> key_script = dict.get_typed_key_script();
if (key_script.is_valid()) {
if (key_script->get_global_name() != StringName()) {
result += key_script->get_global_name();
} else if (!key_script->get_path().get_file().is_empty()) {
result += key_script->get_path().get_file();
} else {
result += dict.get_typed_key_class_name();
}
} else if (dict.get_typed_key_class_name() != StringName()) {
result += dict.get_typed_key_class_name();
} else if (dict.is_typed_key()) {
result += Variant::get_type_name((Variant::Type)dict.get_typed_key_builtin());
} else {
result += "Variant";
}
result += ", ";
Ref<Script> value_script = dict.get_typed_value_script();
if (value_script.is_valid()) {
if (value_script->get_global_name() != StringName()) {
result += value_script->get_global_name();
} else if (!value_script->get_path().get_file().is_empty()) {
result += value_script->get_path().get_file();
} else {
result += dict.get_typed_value_class_name();
}
} else if (dict.get_typed_value_class_name() != StringName()) {
result += dict.get_typed_value_class_name();
} else if (dict.is_typed_value()) {
result += Variant::get_type_name((Variant::Type)dict.get_typed_value_builtin());
} else {
result += "Variant";
}
result += "](";
}
if (dict.is_empty()) {
return "{}";
}
if (p_recursion_level > MAX_RECURSION_LEVEL) {
return "{...}";
}
result += "{}";
} else if (p_recursion_level > MAX_RECURSION_LEVEL) {
result += "{...}";
} else {
result += "{";
List<Variant> keys;
dict.get_key_list(&keys);
keys.sort();
String data;
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
if (E->prev()) {
data += ", ";
result += ", ";
}
data += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1);
result += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1);
}
return "{" + data + "}";
result += "}";
}
if (dict.is_typed()) {
result += ")";
}
return result;
} break;
case Variant::ARRAY: {
const Array array = p_variant;
String result;
if (array.get_typed_builtin() != Variant::NIL) {
if (array.is_typed()) {
result += "Array[";
Ref<Script> script = array.get_typed_script();
@ -209,16 +266,18 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re
result += "[...]";
} else {
result += "[";
for (int i = 0; i < array.size(); i++) {
if (i > 0) {
result += ", ";
}
result += _docvalue_from_variant(array[i], p_recursion_level + 1);
}
result += "]";
}
if (array.get_typed_builtin() != Variant::NIL) {
if (array.is_typed()) {
result += ")";
}

View File

@ -724,6 +724,18 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
result.set_container_element_type(0, container_type);
}
}
if (result.builtin_type == Variant::DICTIONARY) {
GDScriptParser::DataType key_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(0)));
if (key_type.kind != GDScriptParser::DataType::VARIANT) {
key_type.is_constant = false;
result.set_container_element_type(0, key_type);
}
GDScriptParser::DataType value_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(1)));
if (value_type.kind != GDScriptParser::DataType::VARIANT) {
value_type.is_constant = false;
result.set_container_element_type(1, value_type);
}
}
} else if (class_exists(first)) {
// Native engine classes.
result.kind = GDScriptParser::DataType::NATIVE;
@ -884,11 +896,16 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type
if (!p_type->container_types.is_empty()) {
if (result.builtin_type == Variant::ARRAY) {
if (p_type->container_types.size() != 1) {
push_error("Arrays require exactly one collection element type.", p_type);
push_error(R"(Typed arrays require exactly one collection element type.)", p_type);
return bad_type;
}
} else if (result.builtin_type == Variant::DICTIONARY) {
if (p_type->container_types.size() != 2) {
push_error(R"(Typed dictionaries require exactly two collection element types.)", p_type);
return bad_type;
}
} else {
push_error("Only arrays can specify collection element types.", p_type);
push_error(R"(Only arrays and dictionaries can specify collection element types.)", p_type);
return bad_type;
}
}
@ -1926,6 +1943,11 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
if (has_specified_type && specified_type.has_container_element_type(0)) {
update_array_literal_element_type(array, specified_type.get_container_element_type(0));
}
} else if (p_assignable->initializer->type == GDScriptParser::Node::DICTIONARY) {
GDScriptParser::DictionaryNode *dictionary = static_cast<GDScriptParser::DictionaryNode *>(p_assignable->initializer);
if (has_specified_type && specified_type.has_container_element_types()) {
update_dictionary_literal_element_type(dictionary, specified_type.get_container_element_type_or_variant(0), specified_type.get_container_element_type_or_variant(1));
}
}
if (is_constant && !p_assignable->initializer->is_constant) {
@ -1987,7 +2009,7 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
} else {
push_error(vformat(R"(Cannot assign a value of type %s to %s "%s" with specified type %s.)", initializer_type.to_string(), p_kind, p_assignable->identifier->name, specified_type.to_string()), p_assignable->initializer);
}
} else if (specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) {
} else if ((specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) || (specified_type.has_container_element_type(1) && !initializer_type.has_container_element_type(1))) {
mark_node_unsafe(p_assignable->initializer);
#ifdef DEBUG_ENABLED
} else if (specified_type.builtin_type == Variant::INT && initializer_type.builtin_type == Variant::FLOAT) {
@ -2229,8 +2251,12 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
} else if (!is_type_compatible(specified_type, variable_type)) {
p_for->use_conversion_assign = true;
}
if (p_for->list && p_for->list->type == GDScriptParser::Node::ARRAY) {
if (p_for->list) {
if (p_for->list->type == GDScriptParser::Node::ARRAY) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type);
} else if (p_for->list->type == GDScriptParser::Node::DICTIONARY) {
update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_for->list), specified_type, GDScriptParser::DataType::get_variant_type());
}
}
}
p_for->variable->set_datatype(specified_type);
@ -2432,6 +2458,9 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
} else {
if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type(0)) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type(0));
} else if (p_return->return_value->type == GDScriptParser::Node::DICTIONARY && has_expected_type && expected_type.has_container_element_types()) {
update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_return->return_value),
expected_type.get_container_element_type_or_variant(0), expected_type.get_container_element_type_or_variant(1));
}
if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) {
update_const_expression_builtin_type(p_return->return_value, expected_type, "return");
@ -2678,6 +2707,54 @@ void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNo
p_array->set_datatype(array_type);
}
// When a dictionary literal is stored (or passed as function argument) to a typed context, we then assume the dictionary is typed.
// This function determines which type is that (if any).
void GDScriptAnalyzer::update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type) {
GDScriptParser::DataType expected_key_type = p_key_element_type;
GDScriptParser::DataType expected_value_type = p_value_element_type;
expected_key_type.container_element_types.clear(); // Nested types (like `Dictionary[String, Array[int]]`) are not currently supported.
expected_value_type.container_element_types.clear();
for (int i = 0; i < p_dictionary->elements.size(); i++) {
GDScriptParser::ExpressionNode *key_element_node = p_dictionary->elements[i].key;
if (key_element_node->is_constant) {
update_const_expression_builtin_type(key_element_node, expected_key_type, "include");
}
const GDScriptParser::DataType &actual_key_type = key_element_node->get_datatype();
if (actual_key_type.has_no_type() || actual_key_type.is_variant() || !actual_key_type.is_hard_type()) {
mark_node_unsafe(key_element_node);
} else if (!is_type_compatible(expected_key_type, actual_key_type, true, p_dictionary)) {
if (is_type_compatible(actual_key_type, expected_key_type)) {
mark_node_unsafe(key_element_node);
} else {
push_error(vformat(R"(Cannot have a key of type "%s" in a dictionary of type "Dictionary[%s, %s]".)", actual_key_type.to_string(), expected_key_type.to_string(), expected_value_type.to_string()), key_element_node);
return;
}
}
GDScriptParser::ExpressionNode *value_element_node = p_dictionary->elements[i].value;
if (value_element_node->is_constant) {
update_const_expression_builtin_type(value_element_node, expected_value_type, "include");
}
const GDScriptParser::DataType &actual_value_type = value_element_node->get_datatype();
if (actual_value_type.has_no_type() || actual_value_type.is_variant() || !actual_value_type.is_hard_type()) {
mark_node_unsafe(value_element_node);
} else if (!is_type_compatible(expected_value_type, actual_value_type, true, p_dictionary)) {
if (is_type_compatible(actual_value_type, expected_value_type)) {
mark_node_unsafe(value_element_node);
} else {
push_error(vformat(R"(Cannot have a value of type "%s" in a dictionary of type "Dictionary[%s, %s]".)", actual_value_type.to_string(), expected_key_type.to_string(), expected_value_type.to_string()), value_element_node);
return;
}
}
}
GDScriptParser::DataType dictionary_type = p_dictionary->get_datatype();
dictionary_type.set_container_element_type(0, expected_key_type);
dictionary_type.set_container_element_type(1, expected_value_type);
p_dictionary->set_datatype(dictionary_type);
}
void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) {
reduce_expression(p_assignment->assigned_value);
@ -2770,9 +2847,12 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
}
}
// Check if assigned value is an array literal, so we can make it a typed array too if appropriate.
// Check if assigned value is an array/dictionary literal, so we can make it a typed container too if appropriate.
if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.is_hard_type() && assignee_type.has_container_element_type(0)) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type(0));
} else if (p_assignment->assigned_value->type == GDScriptParser::Node::DICTIONARY && assignee_type.is_hard_type() && assignee_type.has_container_element_types()) {
update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_assignment->assigned_value),
assignee_type.get_container_element_type_or_variant(0), assignee_type.get_container_element_type_or_variant(1));
}
if (p_assignment->operation == GDScriptParser::AssignmentNode::OP_NONE && assignee_type.is_hard_type() && p_assignment->assigned_value->is_constant) {
@ -2850,8 +2930,8 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
// weak non-variant assignee and incompatible result
downgrades_assignee = true;
}
} else if (assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) {
// typed array assignee and untyped array result
} else if ((assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) || (assignee_type.has_container_element_type(1) && !op_type.has_container_element_type(1))) {
// Typed assignee and untyped result.
mark_node_unsafe(p_assignment);
}
}
@ -3049,10 +3129,13 @@ const char *check_for_renamed_identifier(String identifier, GDScriptParser::Node
void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) {
bool all_is_constant = true;
HashMap<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing.
HashMap<int, GDScriptParser::DictionaryNode *> dictionaries; // Same, but for dictionaries.
for (int i = 0; i < p_call->arguments.size(); i++) {
reduce_expression(p_call->arguments[i]);
if (p_call->arguments[i]->type == GDScriptParser::Node::ARRAY) {
arrays[i] = static_cast<GDScriptParser::ArrayNode *>(p_call->arguments[i]);
} else if (p_call->arguments[i]->type == GDScriptParser::Node::DICTIONARY) {
dictionaries[i] = static_cast<GDScriptParser::DictionaryNode *>(p_call->arguments[i]);
}
all_is_constant = all_is_constant && p_call->arguments[i]->is_constant;
}
@ -3437,6 +3520,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
update_array_literal_element_type(E.value, par_types.get(index).get_container_element_type(0));
}
}
for (const KeyValue<int, GDScriptParser::DictionaryNode *> &E : dictionaries) {
int index = E.key;
if (index < par_types.size() && par_types.get(index).is_hard_type() && par_types.get(index).has_container_element_types()) {
GDScriptParser::DataType key = par_types.get(index).get_container_element_type_or_variant(0);
GDScriptParser::DataType value = par_types.get(index).get_container_element_type_or_variant(1);
update_dictionary_literal_element_type(E.value, key, value);
}
}
validate_call_arg(par_types, default_arg_count, method_flags.has_flag(METHOD_FLAG_VARARG), p_call);
if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) {
@ -3567,6 +3658,11 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_cast->operand), cast_type.get_container_element_type(0));
}
if (p_cast->operand->type == GDScriptParser::Node::DICTIONARY && cast_type.has_container_element_types()) {
update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_cast->operand),
cast_type.get_container_element_type_or_variant(0), cast_type.get_container_element_type_or_variant(1));
}
if (!cast_type.is_variant()) {
GDScriptParser::DataType op_type = p_cast->operand->get_datatype();
if (op_type.is_variant() || !op_type.is_hard_type()) {
@ -4591,10 +4687,23 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
reduce_identifier_from_base(p_subscript->attribute, &base_type);
GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype();
if (attr_type.is_set()) {
if (base_type.builtin_type == Variant::DICTIONARY && base_type.has_container_element_types()) {
Variant::Type key_type = base_type.get_container_element_type_or_variant(0).builtin_type;
valid = key_type == Variant::NIL || key_type == Variant::STRING || key_type == Variant::STRING_NAME;
if (base_type.has_container_element_type(1)) {
result_type = base_type.get_container_element_type(1);
result_type.type_source = base_type.type_source;
} else {
result_type.builtin_type = Variant::NIL;
result_type.kind = GDScriptParser::DataType::VARIANT;
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
}
} else {
valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type;
result_type = attr_type;
p_subscript->is_constant = p_subscript->attribute->is_constant;
p_subscript->reduced_value = p_subscript->attribute->reduced_value;
}
} else if (!base_type.is_meta_type || !base_type.is_constant) {
valid = base_type.kind != GDScriptParser::DataType::BUILTIN;
#ifdef DEBUG_ENABLED
@ -4701,8 +4810,40 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
case Variant::SIGNAL:
case Variant::STRING_NAME:
break;
// Here for completeness.
// Support depends on if the dictionary has a typed key, otherwise anything is valid.
case Variant::DICTIONARY:
if (base_type.has_container_element_type(0)) {
GDScriptParser::DataType key_type = base_type.get_container_element_type(0);
switch (index_type.builtin_type) {
// Null value will be treated as an empty object, allow.
case Variant::NIL:
error = key_type.builtin_type != Variant::OBJECT;
break;
// Objects are parsed for validity in a similar manner to container types.
case Variant::OBJECT:
if (key_type.builtin_type == Variant::OBJECT) {
error = !key_type.can_reference(index_type);
} else {
error = key_type.builtin_type != Variant::NIL;
}
break;
// String and StringName interchangeable in this context.
case Variant::STRING:
case Variant::STRING_NAME:
error = key_type.builtin_type != Variant::STRING_NAME && key_type.builtin_type != Variant::STRING;
break;
// Ints are valid indices for floats, but not the other way around.
case Variant::INT:
error = key_type.builtin_type != Variant::INT && key_type.builtin_type != Variant::FLOAT;
break;
// All other cases require the types to match exactly.
default:
error = key_type.builtin_type != index_type.builtin_type;
break;
}
}
break;
// Here for completeness.
case Variant::VARIANT_MAX:
break;
}
@ -4791,7 +4932,6 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
case Variant::PROJECTION:
case Variant::PLANE:
case Variant::COLOR:
case Variant::DICTIONARY:
case Variant::OBJECT:
result_type.kind = GDScriptParser::DataType::VARIANT;
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
@ -4806,6 +4946,16 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
}
break;
// Can have two element types, but we only care about the value.
case Variant::DICTIONARY:
if (base_type.has_container_element_type(1)) {
result_type = base_type.get_container_element_type(1);
result_type.type_source = base_type.type_source;
} else {
result_type.kind = GDScriptParser::DataType::VARIANT;
result_type.type_source = GDScriptParser::DataType::UNDETECTED;
}
break;
// Here for completeness.
case Variant::VARIANT_MAX:
break;
@ -4985,7 +5135,9 @@ Variant GDScriptAnalyzer::make_array_reduced_value(GDScriptParser::ArrayNode *p_
}
Variant GDScriptAnalyzer::make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &is_reduced) {
Dictionary dictionary;
Dictionary dictionary = p_dictionary->get_datatype().has_container_element_types()
? make_dictionary_from_element_datatype(p_dictionary->get_datatype().get_container_element_type_or_variant(0), p_dictionary->get_datatype().get_container_element_type_or_variant(1))
: Dictionary();
for (int i = 0; i < p_dictionary->elements.size(); i++) {
const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i];
@ -5072,6 +5224,49 @@ Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::D
return array;
}
Dictionary GDScriptAnalyzer::make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node) {
Dictionary dictionary;
StringName key_name;
Variant key_script;
StringName value_name;
Variant value_script;
if (p_key_element_datatype.builtin_type == Variant::OBJECT) {
Ref<Script> script_type = p_key_element_datatype.script_type;
if (p_key_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) {
Error err = OK;
Ref<GDScript> scr = get_depended_shallow_script(p_key_element_datatype.script_path, err);
if (err) {
push_error(vformat(R"(Error while getting cache for script "%s".)", p_key_element_datatype.script_path), p_source_node);
return dictionary;
}
script_type.reference_ptr(scr->find_class(p_key_element_datatype.class_type->fqcn));
}
key_name = p_key_element_datatype.native_type;
key_script = script_type;
}
if (p_value_element_datatype.builtin_type == Variant::OBJECT) {
Ref<Script> script_type = p_value_element_datatype.script_type;
if (p_value_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) {
Error err = OK;
Ref<GDScript> scr = get_depended_shallow_script(p_value_element_datatype.script_path, err);
if (err) {
push_error(vformat(R"(Error while getting cache for script "%s".)", p_value_element_datatype.script_path), p_source_node);
return dictionary;
}
script_type.reference_ptr(scr->find_class(p_value_element_datatype.class_type->fqcn));
}
value_name = p_value_element_datatype.native_type;
value_script = script_type;
}
dictionary.set_typed(p_key_element_datatype.builtin_type, key_name, key_script, p_value_element_datatype.builtin_type, value_name, value_script);
return dictionary;
}
Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNode *p_variable) {
Variant result = Variant();
@ -5087,6 +5282,10 @@ Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNo
if (datatype.kind == GDScriptParser::DataType::BUILTIN && datatype.builtin_type != Variant::OBJECT) {
if (datatype.builtin_type == Variant::ARRAY && datatype.has_container_element_type(0)) {
result = make_array_from_element_datatype(datatype.get_container_element_type(0));
} else if (datatype.builtin_type == Variant::DICTIONARY && datatype.has_container_element_types()) {
GDScriptParser::DataType key = datatype.get_container_element_type_or_variant(0);
GDScriptParser::DataType value = datatype.get_container_element_type_or_variant(1);
result = make_dictionary_from_element_datatype(key, value);
} else {
VariantInternal::initialize(&result, datatype.builtin_type);
}
@ -5115,6 +5314,22 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va
} else if (array.get_typed_builtin() != Variant::NIL) {
result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)array.get_typed_builtin())));
}
} else if (p_value.get_type() == Variant::DICTIONARY) {
const Dictionary &dict = p_value;
if (dict.get_typed_key_script()) {
result.set_container_element_type(0, type_from_metatype(make_script_meta_type(dict.get_typed_key_script())));
} else if (dict.get_typed_key_class_name()) {
result.set_container_element_type(0, type_from_metatype(make_native_meta_type(dict.get_typed_key_class_name())));
} else if (dict.get_typed_key_builtin() != Variant::NIL) {
result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)dict.get_typed_key_builtin())));
}
if (dict.get_typed_value_script()) {
result.set_container_element_type(1, type_from_metatype(make_script_meta_type(dict.get_typed_value_script())));
} else if (dict.get_typed_value_class_name()) {
result.set_container_element_type(1, type_from_metatype(make_native_meta_type(dict.get_typed_value_class_name())));
} else if (dict.get_typed_value_builtin() != Variant::NIL) {
result.set_container_element_type(1, type_from_metatype(make_builtin_meta_type((Variant::Type)dict.get_typed_value_builtin())));
}
} else if (p_value.get_type() == Variant::OBJECT) {
// Object is treated as a native type, not a builtin type.
result.kind = GDScriptParser::DataType::NATIVE;
@ -5247,6 +5462,60 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo
}
elem_type.is_constant = false;
result.set_container_element_type(0, elem_type);
} else if (p_property.type == Variant::DICTIONARY && p_property.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
// Check element type.
StringName key_elem_type_name = p_property.hint_string.get_slice(";", 0);
GDScriptParser::DataType key_elem_type;
key_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
Variant::Type key_elem_builtin_type = GDScriptParser::get_builtin_type(key_elem_type_name);
if (key_elem_builtin_type < Variant::VARIANT_MAX) {
// Builtin type.
key_elem_type.kind = GDScriptParser::DataType::BUILTIN;
key_elem_type.builtin_type = key_elem_builtin_type;
} else if (class_exists(key_elem_type_name)) {
key_elem_type.kind = GDScriptParser::DataType::NATIVE;
key_elem_type.builtin_type = Variant::OBJECT;
key_elem_type.native_type = key_elem_type_name;
} else if (ScriptServer::is_global_class(key_elem_type_name)) {
// Just load this as it shouldn't be a GDScript.
Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(key_elem_type_name));
key_elem_type.kind = GDScriptParser::DataType::SCRIPT;
key_elem_type.builtin_type = Variant::OBJECT;
key_elem_type.native_type = script->get_instance_base_type();
key_elem_type.script_type = script;
} else {
ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary.");
}
key_elem_type.is_constant = false;
StringName value_elem_type_name = p_property.hint_string.get_slice(";", 1);
GDScriptParser::DataType value_elem_type;
value_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
Variant::Type value_elem_builtin_type = GDScriptParser::get_builtin_type(value_elem_type_name);
if (value_elem_builtin_type < Variant::VARIANT_MAX) {
// Builtin type.
value_elem_type.kind = GDScriptParser::DataType::BUILTIN;
value_elem_type.builtin_type = value_elem_builtin_type;
} else if (class_exists(value_elem_type_name)) {
value_elem_type.kind = GDScriptParser::DataType::NATIVE;
value_elem_type.builtin_type = Variant::OBJECT;
value_elem_type.native_type = value_elem_type_name;
} else if (ScriptServer::is_global_class(value_elem_type_name)) {
// Just load this as it shouldn't be a GDScript.
Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(value_elem_type_name));
value_elem_type.kind = GDScriptParser::DataType::SCRIPT;
value_elem_type.builtin_type = Variant::OBJECT;
value_elem_type.native_type = script->get_instance_base_type();
value_elem_type.script_type = script;
} else {
ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary.");
}
value_elem_type.is_constant = false;
result.set_container_element_type(0, key_elem_type);
result.set_container_element_type(1, value_elem_type);
} else if (p_property.type == Variant::INT) {
// Check if it's enum.
if ((p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && p_property.class_name != StringName()) {
@ -5667,6 +5936,15 @@ bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType &
valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0);
}
}
if (valid && p_target.builtin_type == Variant::DICTIONARY && p_source.builtin_type == Variant::DICTIONARY) {
// Check the element types.
if (p_target.has_container_element_type(0) && p_source.has_container_element_type(0)) {
valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0);
}
if (valid && p_target.has_container_element_type(1) && p_source.has_container_element_type(1)) {
valid = p_target.get_container_element_type(1) == p_source.get_container_element_type(1);
}
}
return valid;
}

View File

@ -125,6 +125,7 @@ class GDScriptAnalyzer {
// Helpers.
Array make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
Dictionary make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node = nullptr);
GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source);
static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type);
GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const;
@ -137,6 +138,7 @@ class GDScriptAnalyzer {
GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source);
void update_const_expression_builtin_type(GDScriptParser::ExpressionNode *p_expression, const GDScriptParser::DataType &p_type, const char *p_usage, bool p_is_cast = false);
void update_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type);
void update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type);
bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr);
void push_error(const String &p_message, const GDScriptParser::Node *p_origin = nullptr);
void mark_node_unsafe(const GDScriptParser::Node *p_node);

View File

@ -634,6 +634,18 @@ void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const A
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
} else if (p_type.builtin_type == Variant::DICTIONARY && p_type.has_container_element_types()) {
const GDScriptDataType &key_element_type = p_type.get_container_element_type_or_variant(0);
const GDScriptDataType &value_element_type = p_type.get_container_element_type_or_variant(1);
append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_DICTIONARY);
append(p_target);
append(p_source);
append(get_constant_pos(key_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(get_constant_pos(value_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(key_element_type.builtin_type);
append(key_element_type.native_type);
append(value_element_type.builtin_type);
append(value_element_type.native_type);
} else {
append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_BUILTIN);
append(p_target);
@ -889,6 +901,18 @@ void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_ta
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
} else if (p_target.type.builtin_type == Variant::DICTIONARY && p_target.type.has_container_element_types()) {
const GDScriptDataType &key_type = p_target.type.get_container_element_type_or_variant(0);
const GDScriptDataType &value_type = p_target.type.get_container_element_type_or_variant(1);
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_DICTIONARY);
append(p_target);
append(p_source);
append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(key_type.builtin_type);
append(key_type.native_type);
append(value_type.builtin_type);
append(value_type.native_type);
} else {
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN);
append(p_target);
@ -935,6 +959,18 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
} else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::DICTIONARY && p_target.type.has_container_element_types()) {
const GDScriptDataType &key_type = p_target.type.get_container_element_type_or_variant(0);
const GDScriptDataType &value_type = p_target.type.get_container_element_type_or_variant(1);
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_DICTIONARY);
append(p_target);
append(p_source);
append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(key_type.builtin_type);
append(key_type.native_type);
append(value_type.builtin_type);
append(value_type.native_type);
} else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) {
// Need conversion.
append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN);
@ -1434,6 +1470,23 @@ void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_targ
ct.cleanup();
}
void GDScriptByteCodeGenerator::write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) {
append_opcode_and_argcount(GDScriptFunction::OPCODE_CONSTRUCT_TYPED_DICTIONARY, 3 + p_arguments.size());
for (int i = 0; i < p_arguments.size(); i++) {
append(p_arguments[i]);
}
CallTarget ct = get_call_target(p_target);
append(ct.target);
append(get_constant_pos(p_key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(get_constant_pos(p_value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(p_arguments.size() / 2); // This is number of key-value pairs, so only half of actual arguments.
append(p_key_type.builtin_type);
append(p_key_type.native_type);
append(p_value_type.builtin_type);
append(p_value_type.native_type);
ct.cleanup();
}
void GDScriptByteCodeGenerator::write_await(const Address &p_target, const Address &p_operand) {
append_opcode(GDScriptFunction::OPCODE_AWAIT);
append(p_operand);
@ -1711,6 +1764,19 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) {
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
} else if (function->return_type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type == Variant::DICTIONARY &&
function->return_type.has_container_element_types()) {
// Typed dictionary.
const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0);
const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1);
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY);
append(p_return_value);
append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(key_type.builtin_type);
append(key_type.native_type);
append(value_type.builtin_type);
append(value_type.native_type);
} else if (function->return_type.kind == GDScriptDataType::BUILTIN && p_return_value.type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type != p_return_value.type.builtin_type) {
// Add conversion.
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN);
@ -1735,6 +1801,17 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) {
append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(element_type.builtin_type);
append(element_type.native_type);
} else if (function->return_type.builtin_type == Variant::DICTIONARY && function->return_type.has_container_element_types()) {
const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0);
const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1);
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY);
append(p_return_value);
append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS));
append(key_type.builtin_type);
append(key_type.native_type);
append(value_type.builtin_type);
append(value_type.native_type);
} else {
append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN);
append(p_return_value);
@ -1803,6 +1880,13 @@ void GDScriptByteCodeGenerator::clear_address(const Address &p_address) {
case Variant::BOOL:
write_assign_false(p_address);
break;
case Variant::DICTIONARY:
if (p_address.type.has_container_element_types()) {
write_construct_typed_dictionary(p_address, p_address.type.get_container_element_type_or_variant(0), p_address.type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>());
} else {
write_construct(p_address, p_address.type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
}
break;
case Variant::ARRAY:
if (p_address.type.has_container_element_type(0)) {
write_construct_typed_array(p_address, p_address.type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());

View File

@ -529,6 +529,7 @@ public:
virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) override;
virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) override;
virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) override;
virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) override;
virtual void write_await(const Address &p_target, const Address &p_operand) override;
virtual void write_if(const Address &p_condition) override;
virtual void write_else() override;

View File

@ -142,6 +142,7 @@ public:
virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) = 0;
virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) = 0;
virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) = 0;
virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) = 0;
virtual void write_await(const Address &p_target, const Address &p_operand) = 0;
virtual void write_if(const Address &p_condition) = 0;
virtual void write_else() = 0;

View File

@ -532,10 +532,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
Vector<GDScriptCodeGenerator::Address> elements;
// Create the result temporary first since it's the last to be killed.
GDScriptDataType dict_type;
dict_type.has_type = true;
dict_type.kind = GDScriptDataType::BUILTIN;
dict_type.builtin_type = Variant::DICTIONARY;
GDScriptDataType dict_type = _gdtype_from_datatype(dn->get_datatype(), codegen.script);
GDScriptCodeGenerator::Address result = codegen.add_temporary(dict_type);
for (int i = 0; i < dn->elements.size(); i++) {
@ -566,7 +563,11 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
elements.push_back(element);
}
if (dict_type.has_container_element_types()) {
gen->write_construct_typed_dictionary(result, dict_type.get_container_element_type_or_variant(0), dict_type.get_container_element_type_or_variant(1), elements);
} else {
gen->write_construct_dictionary(result, elements);
}
for (int i = 0; i < elements.size(); i++) {
if (elements[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) {
@ -2325,8 +2326,11 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type);
if (field_type.has_container_element_type(0)) {
if (field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type(0)) {
codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
} else if (field_type.builtin_type == Variant::DICTIONARY && field_type.has_container_element_types()) {
codegen.generator->write_construct_typed_dictionary(dst_address, field_type.get_container_element_type_or_variant(0),
field_type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>());
} else if (field_type.kind == GDScriptDataType::BUILTIN) {
codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());
}
@ -2515,11 +2519,17 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS
if (field_type.has_type) {
codegen.generator->write_newline(field->start_line);
if (field_type.has_container_element_type(0)) {
if (field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type(0)) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
codegen.generator->write_construct_typed_array(temp, field_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>());
codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index);
codegen.generator->pop_temporary();
} else if (field_type.builtin_type == Variant::DICTIONARY && field_type.has_container_element_types()) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
codegen.generator->write_construct_typed_dictionary(temp, field_type.get_container_element_type_or_variant(0),
field_type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>());
codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index);
codegen.generator->pop_temporary();
} else if (field_type.kind == GDScriptDataType::BUILTIN) {
GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type);
codegen.generator->write_construct(temp, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>());

View File

@ -176,6 +176,47 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 6;
} break;
case OPCODE_TYPE_TEST_DICTIONARY: {
text += "type test ";
text += DADDR(1);
text += " = ";
text += DADDR(2);
text += " is Dictionary[";
Ref<Script> key_script_type = get_constant(_code_ptr[ip + 3] & ADDR_MASK);
Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5];
StringName key_native_type = get_global_name(_code_ptr[ip + 6]);
if (key_script_type.is_valid() && key_script_type->is_valid()) {
text += "script(";
text += GDScript::debug_get_script_name(key_script_type);
text += ")";
} else if (key_native_type != StringName()) {
text += key_native_type;
} else {
text += Variant::get_type_name(key_builtin_type);
}
text += ", ";
Ref<Script> value_script_type = get_constant(_code_ptr[ip + 4] & ADDR_MASK);
Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7];
StringName value_native_type = get_global_name(_code_ptr[ip + 8]);
if (value_script_type.is_valid() && value_script_type->is_valid()) {
text += "script(";
text += GDScript::debug_get_script_name(value_script_type);
text += ")";
} else if (value_native_type != StringName()) {
text += value_native_type;
} else {
text += Variant::get_type_name(value_builtin_type);
}
text += "]";
incr += 9;
} break;
case OPCODE_TYPE_TEST_NATIVE: {
text += "type test ";
text += DADDR(1);
@ -399,6 +440,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 6;
} break;
case OPCODE_ASSIGN_TYPED_DICTIONARY: {
text += "assign typed dictionary ";
text += DADDR(1);
text += " = ";
text += DADDR(2);
incr += 9;
} break;
case OPCODE_ASSIGN_TYPED_NATIVE: {
text += "assign typed native (";
text += DADDR(3);
@ -564,6 +613,58 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 3 + argc * 2;
} break;
case OPCODE_CONSTRUCT_TYPED_DICTIONARY: {
int instr_var_args = _code_ptr[++ip];
int argc = _code_ptr[ip + 1 + instr_var_args];
Ref<Script> key_script_type = get_constant(_code_ptr[ip + argc * 2 + 2] & ADDR_MASK);
Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + argc * 2 + 5];
StringName key_native_type = get_global_name(_code_ptr[ip + argc * 2 + 6]);
String key_type_name;
if (key_script_type.is_valid() && key_script_type->is_valid()) {
key_type_name = "script(" + GDScript::debug_get_script_name(key_script_type) + ")";
} else if (key_native_type != StringName()) {
key_type_name = key_native_type;
} else {
key_type_name = Variant::get_type_name(key_builtin_type);
}
Ref<Script> value_script_type = get_constant(_code_ptr[ip + argc * 2 + 3] & ADDR_MASK);
Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + argc * 2 + 7];
StringName value_native_type = get_global_name(_code_ptr[ip + argc * 2 + 8]);
String value_type_name;
if (value_script_type.is_valid() && value_script_type->is_valid()) {
value_type_name = "script(" + GDScript::debug_get_script_name(value_script_type) + ")";
} else if (value_native_type != StringName()) {
value_type_name = value_native_type;
} else {
value_type_name = Variant::get_type_name(value_builtin_type);
}
text += "make_typed_dict (";
text += key_type_name;
text += ", ";
text += value_type_name;
text += ") ";
text += DADDR(1 + argc * 2);
text += " = {";
for (int i = 0; i < argc; i++) {
if (i > 0) {
text += ", ";
}
text += DADDR(1 + i * 2 + 0);
text += ": ";
text += DADDR(1 + i * 2 + 1);
}
text += "}";
incr += 9 + argc * 2;
} break;
case OPCODE_CALL:
case OPCODE_CALL_RETURN:
case OPCODE_CALL_ASYNC: {
@ -978,6 +1079,12 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const {
incr += 5;
} break;
case OPCODE_RETURN_TYPED_DICTIONARY: {
text += "return typed dictionary ";
text += DADDR(1);
incr += 8;
} break;
case OPCODE_RETURN_TYPED_NATIVE: {
text += "return typed native (";
text += DADDR(2);

View File

@ -697,6 +697,10 @@ static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg, co
return _trim_parent_class(class_name, p_base_class);
} else if (p_info.type == Variant::ARRAY && p_info.hint == PROPERTY_HINT_ARRAY_TYPE && !p_info.hint_string.is_empty()) {
return "Array[" + _trim_parent_class(p_info.hint_string, p_base_class) + "]";
} else if (p_info.type == Variant::DICTIONARY && p_info.hint == PROPERTY_HINT_DICTIONARY_TYPE && !p_info.hint_string.is_empty()) {
const String key = p_info.hint_string.get_slice(";", 0);
const String value = p_info.hint_string.get_slice(";", 1);
return "Dictionary[" + _trim_parent_class(key, p_base_class) + ", " + _trim_parent_class(value, p_base_class) + "]";
} else if (p_info.type == Variant::NIL) {
if (p_is_arg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) {
return "Variant";

View File

@ -93,6 +93,41 @@ public:
} else {
valid = false;
}
} else if (valid && builtin_type == Variant::DICTIONARY && has_container_element_types()) {
Dictionary dictionary = p_variant;
if (dictionary.is_typed()) {
if (dictionary.is_typed_key()) {
GDScriptDataType key = get_container_element_type_or_variant(0);
Variant::Type key_builtin_type = (Variant::Type)dictionary.get_typed_key_builtin();
StringName key_native_type = dictionary.get_typed_key_class_name();
Ref<Script> key_script_type_ref = dictionary.get_typed_key_script();
if (key_script_type_ref.is_valid()) {
valid = (key.kind == SCRIPT || key.kind == GDSCRIPT) && key.script_type == key_script_type_ref.ptr();
} else if (key_native_type != StringName()) {
valid = key.kind == NATIVE && key.native_type == key_native_type;
} else {
valid = key.kind == BUILTIN && key.builtin_type == key_builtin_type;
}
}
if (valid && dictionary.is_typed_value()) {
GDScriptDataType value = get_container_element_type_or_variant(1);
Variant::Type value_builtin_type = (Variant::Type)dictionary.get_typed_value_builtin();
StringName value_native_type = dictionary.get_typed_value_class_name();
Ref<Script> value_script_type_ref = dictionary.get_typed_value_script();
if (value_script_type_ref.is_valid()) {
valid = (value.kind == SCRIPT || value.kind == GDSCRIPT) && value.script_type == value_script_type_ref.ptr();
} else if (value_native_type != StringName()) {
valid = value.kind == NATIVE && value.native_type == value_native_type;
} else {
valid = value.kind == BUILTIN && value.builtin_type == value_builtin_type;
}
}
} else {
valid = false;
}
} else if (!valid && p_allow_implicit_conversion) {
valid = Variant::can_convert_strict(var_type, builtin_type);
}
@ -156,6 +191,10 @@ public:
}
return true;
case Variant::DICTIONARY:
if (has_container_element_types()) {
return get_container_element_type_or_variant(0).can_contain_object() || get_container_element_type_or_variant(1).can_contain_object();
}
return true;
case Variant::NIL:
case Variant::OBJECT:
return true;
@ -220,6 +259,7 @@ public:
OPCODE_OPERATOR_VALIDATED,
OPCODE_TYPE_TEST_BUILTIN,
OPCODE_TYPE_TEST_ARRAY,
OPCODE_TYPE_TEST_DICTIONARY,
OPCODE_TYPE_TEST_NATIVE,
OPCODE_TYPE_TEST_SCRIPT,
OPCODE_SET_KEYED,
@ -242,6 +282,7 @@ public:
OPCODE_ASSIGN_FALSE,
OPCODE_ASSIGN_TYPED_BUILTIN,
OPCODE_ASSIGN_TYPED_ARRAY,
OPCODE_ASSIGN_TYPED_DICTIONARY,
OPCODE_ASSIGN_TYPED_NATIVE,
OPCODE_ASSIGN_TYPED_SCRIPT,
OPCODE_CAST_TO_BUILTIN,
@ -252,6 +293,7 @@ public:
OPCODE_CONSTRUCT_ARRAY,
OPCODE_CONSTRUCT_TYPED_ARRAY,
OPCODE_CONSTRUCT_DICTIONARY,
OPCODE_CONSTRUCT_TYPED_DICTIONARY,
OPCODE_CALL,
OPCODE_CALL_RETURN,
OPCODE_CALL_ASYNC,
@ -280,6 +322,7 @@ public:
OPCODE_RETURN,
OPCODE_RETURN_TYPED_BUILTIN,
OPCODE_RETURN_TYPED_ARRAY,
OPCODE_RETURN_TYPED_DICTIONARY,
OPCODE_RETURN_TYPED_NATIVE,
OPCODE_RETURN_TYPED_SCRIPT,
OPCODE_ITERATE_BEGIN,

View File

@ -3554,7 +3554,7 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) {
type->type_chain.push_back(type_element);
if (match(GDScriptTokenizer::Token::BRACKET_OPEN)) {
// Typed collection (like Array[int]).
// Typed collection (like Array[int], Dictionary[String, int]).
bool first_pass = true;
do {
TypeNode *container_type = parse_type(false); // Don't allow void for element type.
@ -4371,6 +4371,14 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
export_type.type_source = variable->datatype.type_source;
}
bool is_dict = false;
if (export_type.builtin_type == Variant::DICTIONARY && export_type.has_container_element_types()) {
is_dict = true;
DataType inner_type = export_type.get_container_element_type_or_variant(1);
export_type = export_type.get_container_element_type_or_variant(0);
export_type.set_container_element_type(0, inner_type); // Store earlier extracted value within key to separately parse after.
}
bool use_default_variable_type_check = true;
if (p_annotation->name == SNAME("@export_range")) {
@ -4398,9 +4406,14 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
}
if (export_type.is_variant() || export_type.has_no_type()) {
if (is_dict) {
// Dictionary allowed to have a variant key/value.
export_type.kind = GDScriptParser::DataType::BUILTIN;
} else {
push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation);
return false;
}
}
switch (export_type.kind) {
case GDScriptParser::DataType::BUILTIN:
@ -4459,6 +4472,90 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta
push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), p_annotation);
return false;
}
if (is_dict) {
String key_prefix = itos(variable->export_info.type);
if (variable->export_info.hint) {
key_prefix += "/" + itos(variable->export_info.hint);
}
key_prefix += ":" + variable->export_info.hint_string;
// Now parse value.
export_type = export_type.get_container_element_type(0);
if (export_type.is_variant() || export_type.has_no_type()) {
export_type.kind = GDScriptParser::DataType::BUILTIN;
}
switch (export_type.kind) {
case GDScriptParser::DataType::BUILTIN:
variable->export_info.type = export_type.builtin_type;
variable->export_info.hint = PROPERTY_HINT_NONE;
variable->export_info.hint_string = String();
break;
case GDScriptParser::DataType::NATIVE:
case GDScriptParser::DataType::SCRIPT:
case GDScriptParser::DataType::CLASS: {
const StringName class_name = _find_narrowest_native_or_global_class(export_type);
if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) {
variable->export_info.type = Variant::OBJECT;
variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE;
variable->export_info.hint_string = class_name;
} else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) {
variable->export_info.type = Variant::OBJECT;
variable->export_info.hint = PROPERTY_HINT_NODE_TYPE;
variable->export_info.hint_string = class_name;
} else {
push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);
return false;
}
} break;
case GDScriptParser::DataType::ENUM: {
if (export_type.is_meta_type) {
variable->export_info.type = Variant::DICTIONARY;
} else {
variable->export_info.type = Variant::INT;
variable->export_info.hint = PROPERTY_HINT_ENUM;
String enum_hint_string;
bool first = true;
for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) {
if (!first) {
enum_hint_string += ",";
} else {
first = false;
}
enum_hint_string += E.key.operator String().capitalize().xml_escape();
enum_hint_string += ":";
enum_hint_string += String::num_int64(E.value).xml_escape();
}
variable->export_info.hint_string = enum_hint_string;
variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM;
variable->export_info.class_name = String(export_type.native_type).replace("::", ".");
}
} break;
default:
push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation);
return false;
}
if (variable->export_info.hint == PROPERTY_HINT_NODE_TYPE && !ClassDB::is_parent_class(p_class->base_type.native_type, SNAME("Node"))) {
push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), p_annotation);
return false;
}
String value_prefix = itos(variable->export_info.type);
if (variable->export_info.hint) {
value_prefix += "/" + itos(variable->export_info.hint);
}
value_prefix += ":" + variable->export_info.hint_string;
variable->export_info.type = Variant::DICTIONARY;
variable->export_info.hint = PROPERTY_HINT_TYPE_STRING;
variable->export_info.hint_string = key_prefix + ";" + value_prefix;
variable->export_info.usage = PROPERTY_USAGE_DEFAULT;
variable->export_info.class_name = StringName();
}
} else if (p_annotation->name == SNAME("@export_enum")) {
use_default_variable_type_check = false;
@ -4780,7 +4877,10 @@ String GDScriptParser::DataType::to_string() const {
return "null";
}
if (builtin_type == Variant::ARRAY && has_container_element_type(0)) {
return vformat("Array[%s]", container_element_types[0].to_string());
return vformat("Array[%s]", get_container_element_type(0).to_string());
}
if (builtin_type == Variant::DICTIONARY && has_container_element_types()) {
return vformat("Dictionary[%s, %s]", get_container_element_type_or_variant(0).to_string(), get_container_element_type_or_variant(1).to_string());
}
return Variant::get_type_name(builtin_type);
case NATIVE:
@ -4869,6 +4969,72 @@ PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) co
case UNRESOLVED:
break;
}
} else if (builtin_type == Variant::DICTIONARY && has_container_element_types()) {
const DataType key_type = get_container_element_type_or_variant(0);
const DataType value_type = get_container_element_type_or_variant(1);
if ((key_type.kind == VARIANT && value_type.kind == VARIANT) || key_type.kind == RESOLVING ||
key_type.kind == UNRESOLVED || value_type.kind == RESOLVING || value_type.kind == UNRESOLVED) {
break;
}
String key_hint, value_hint;
switch (key_type.kind) {
case BUILTIN:
key_hint = Variant::get_type_name(key_type.builtin_type);
break;
case NATIVE:
key_hint = key_type.native_type;
break;
case SCRIPT:
if (key_type.script_type.is_valid() && key_type.script_type->get_global_name() != StringName()) {
key_hint = key_type.script_type->get_global_name();
} else {
key_hint = key_type.native_type;
}
break;
case CLASS:
if (key_type.class_type != nullptr && key_type.class_type->get_global_name() != StringName()) {
key_hint = key_type.class_type->get_global_name();
} else {
key_hint = key_type.native_type;
}
break;
case ENUM:
key_hint = String(key_type.native_type).replace("::", ".");
break;
default:
key_hint = "Variant";
break;
}
switch (value_type.kind) {
case BUILTIN:
value_hint = Variant::get_type_name(value_type.builtin_type);
break;
case NATIVE:
value_hint = value_type.native_type;
break;
case SCRIPT:
if (value_type.script_type.is_valid() && value_type.script_type->get_global_name() != StringName()) {
value_hint = value_type.script_type->get_global_name();
} else {
value_hint = value_type.native_type;
}
break;
case CLASS:
if (value_type.class_type != nullptr && value_type.class_type->get_global_name() != StringName()) {
value_hint = value_type.class_type->get_global_name();
} else {
value_hint = value_type.native_type;
}
break;
case ENUM:
value_hint = String(value_type.native_type).replace("::", ".");
break;
default:
value_hint = "Variant";
break;
}
result.hint = PROPERTY_HINT_DICTIONARY_TYPE;
result.hint_string = key_hint + ";" + value_hint;
}
break;
case NATIVE:
@ -4953,6 +5119,50 @@ GDScriptParser::DataType GDScriptParser::DataType::get_typed_container_type() co
return type;
}
bool GDScriptParser::DataType::can_reference(const GDScriptParser::DataType &p_other) const {
if (p_other.is_meta_type) {
return false;
} else if (builtin_type != p_other.builtin_type) {
return false;
} else if (builtin_type != Variant::OBJECT) {
return true;
}
if (native_type == StringName()) {
return true;
} else if (p_other.native_type == StringName()) {
return false;
} else if (native_type != p_other.native_type && !ClassDB::is_parent_class(p_other.native_type, native_type)) {
return false;
}
Ref<Script> script = script_type;
if (kind == GDScriptParser::DataType::CLASS && script.is_null()) {
Error err = OK;
Ref<GDScript> scr = GDScriptCache::get_shallow_script(script_path, err);
ERR_FAIL_COND_V_MSG(err, false, vformat(R"(Error while getting cache for script "%s".)", script_path));
script.reference_ptr(scr->find_class(class_type->fqcn));
}
Ref<Script> script_other = p_other.script_type;
if (p_other.kind == GDScriptParser::DataType::CLASS && script_other.is_null()) {
Error err = OK;
Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_other.script_path, err);
ERR_FAIL_COND_V_MSG(err, false, vformat(R"(Error while getting cache for script "%s".)", p_other.script_path));
script_other.reference_ptr(scr->find_class(p_other.class_type->fqcn));
}
if (script.is_null()) {
return true;
} else if (script_other.is_null()) {
return false;
} else if (script != script_other && !script_other->inherits_script(script)) {
return false;
}
return true;
}
void GDScriptParser::complete_extents(Node *p_node) {
while (!nodes_in_progress.is_empty() && nodes_in_progress.back()->get() != p_node) {
ERR_PRINT("Parser bug: Mismatch in extents tracking stack.");

View File

@ -189,6 +189,8 @@ public:
GDScriptParser::DataType get_typed_container_type() const;
bool can_reference(const DataType &p_other) const;
bool operator==(const DataType &p_other) const {
if (type_source == UNDETECTED || p_other.type_source == UNDETECTED) {
return true; // Can be considered equal for parsing purposes.

View File

@ -84,9 +84,15 @@ static String _get_var_type(const Variant *p_var) {
if (p_var->get_type() == Variant::ARRAY) {
basestr = "Array";
const Array *p_array = VariantInternal::get_array(p_var);
Variant::Type builtin_type = (Variant::Type)p_array->get_typed_builtin();
if (builtin_type != Variant::NIL) {
basestr += "[" + _get_element_type(builtin_type, p_array->get_typed_class_name(), p_array->get_typed_script()) + "]";
if (p_array->is_typed()) {
basestr += "[" + _get_element_type((Variant::Type)p_array->get_typed_builtin(), p_array->get_typed_class_name(), p_array->get_typed_script()) + "]";
}
} else if (p_var->get_type() == Variant::DICTIONARY) {
basestr = "Dictionary";
const Dictionary *p_dictionary = VariantInternal::get_dictionary(p_var);
if (p_dictionary->is_typed()) {
basestr += "[" + _get_element_type((Variant::Type)p_dictionary->get_typed_key_builtin(), p_dictionary->get_typed_key_class_name(), p_dictionary->get_typed_key_script()) +
", " + _get_element_type((Variant::Type)p_dictionary->get_typed_value_builtin(), p_dictionary->get_typed_value_class_name(), p_dictionary->get_typed_value_script()) + "]";
}
} else {
basestr = Variant::get_type_name(p_var->get_type());
@ -120,6 +126,16 @@ Variant GDScriptFunction::_get_default_variant_for_data_type(const GDScriptDataT
}
return array;
} else if (p_data_type.builtin_type == Variant::DICTIONARY) {
Dictionary dict;
// Typed dictionary.
if (p_data_type.has_container_element_types()) {
const GDScriptDataType &key_type = p_data_type.get_container_element_type_or_variant(0);
const GDScriptDataType &value_type = p_data_type.get_container_element_type_or_variant(1);
dict.set_typed(key_type.builtin_type, key_type.native_type, key_type.script_type, value_type.builtin_type, value_type.native_type, value_type.script_type);
}
return dict;
} else {
Callable::CallError ce;
Variant variant;
@ -153,6 +169,9 @@ String GDScriptFunction::_get_call_error(const String &p_where, const Variant **
if (p_err.expected == Variant::ARRAY && p_argptrs[p_err.argument]->get_type() == p_err.expected) {
return "Invalid type in " + p_where + ". The array of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") does not have the same element type as the expected typed array argument.";
}
if (p_err.expected == Variant::DICTIONARY && p_argptrs[p_err.argument]->get_type() == p_err.expected) {
return "Invalid type in " + p_where + ". The dictionary of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") does not have the same element type as the expected typed dictionary argument.";
}
#endif // DEBUG_ENABLED
return "Invalid type in " + p_where + ". Cannot convert argument " + itos(p_err.argument + 1) + " from " + Variant::get_type_name(p_argptrs[p_err.argument]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + ".";
case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS:
@ -215,6 +234,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_OPERATOR_VALIDATED, \
&&OPCODE_TYPE_TEST_BUILTIN, \
&&OPCODE_TYPE_TEST_ARRAY, \
&&OPCODE_TYPE_TEST_DICTIONARY, \
&&OPCODE_TYPE_TEST_NATIVE, \
&&OPCODE_TYPE_TEST_SCRIPT, \
&&OPCODE_SET_KEYED, \
@ -237,6 +257,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_ASSIGN_FALSE, \
&&OPCODE_ASSIGN_TYPED_BUILTIN, \
&&OPCODE_ASSIGN_TYPED_ARRAY, \
&&OPCODE_ASSIGN_TYPED_DICTIONARY, \
&&OPCODE_ASSIGN_TYPED_NATIVE, \
&&OPCODE_ASSIGN_TYPED_SCRIPT, \
&&OPCODE_CAST_TO_BUILTIN, \
@ -247,6 +268,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_CONSTRUCT_ARRAY, \
&&OPCODE_CONSTRUCT_TYPED_ARRAY, \
&&OPCODE_CONSTRUCT_DICTIONARY, \
&&OPCODE_CONSTRUCT_TYPED_DICTIONARY, \
&&OPCODE_CALL, \
&&OPCODE_CALL_RETURN, \
&&OPCODE_CALL_ASYNC, \
@ -275,6 +297,7 @@ void (*type_init_function_table[])(Variant *) = {
&&OPCODE_RETURN, \
&&OPCODE_RETURN_TYPED_BUILTIN, \
&&OPCODE_RETURN_TYPED_ARRAY, \
&&OPCODE_RETURN_TYPED_DICTIONARY, \
&&OPCODE_RETURN_TYPED_NATIVE, \
&&OPCODE_RETURN_TYPED_SCRIPT, \
&&OPCODE_ITERATE_BEGIN, \
@ -548,7 +571,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
return _get_default_variant_for_data_type(return_type);
}
if (argument_types[i].kind == GDScriptDataType::BUILTIN) {
if (argument_types[i].builtin_type == Variant::ARRAY && argument_types[i].has_container_element_type(0)) {
if (argument_types[i].builtin_type == Variant::DICTIONARY && argument_types[i].has_container_element_types()) {
const GDScriptDataType &arg_key_type = argument_types[i].get_container_element_type_or_variant(0);
const GDScriptDataType &arg_value_type = argument_types[i].get_container_element_type_or_variant(1);
Dictionary dict(p_args[i]->operator Dictionary(), arg_key_type.builtin_type, arg_key_type.native_type, arg_key_type.script_type, arg_value_type.builtin_type, arg_value_type.native_type, arg_value_type.script_type);
memnew_placement(&stack[i + 3], Variant(dict));
} else if (argument_types[i].builtin_type == Variant::ARRAY && argument_types[i].has_container_element_type(0)) {
const GDScriptDataType &arg_type = argument_types[i].container_element_types[0];
Array array(p_args[i]->operator Array(), arg_type.builtin_type, arg_type.native_type, arg_type.script_type);
memnew_placement(&stack[i + 3], Variant(array));
@ -827,6 +855,36 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
OPCODE(OPCODE_TYPE_TEST_DICTIONARY) {
CHECK_SPACE(9);
GET_VARIANT_PTR(dst, 0);
GET_VARIANT_PTR(value, 1);
GET_VARIANT_PTR(key_script_type, 2);
Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5];
int key_native_type_idx = _code_ptr[ip + 6];
GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
const StringName key_native_type = _global_names_ptr[key_native_type_idx];
GET_VARIANT_PTR(value_script_type, 3);
Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7];
int value_native_type_idx = _code_ptr[ip + 8];
GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
const StringName value_native_type = _global_names_ptr[value_native_type_idx];
bool result = false;
if (value->get_type() == Variant::DICTIONARY) {
Dictionary *dictionary = VariantInternal::get_dictionary(value);
result = dictionary->get_typed_key_builtin() == ((uint32_t)key_builtin_type) && dictionary->get_typed_key_class_name() == key_native_type && dictionary->get_typed_key_script() == *key_script_type &&
dictionary->get_typed_value_builtin() == ((uint32_t)value_builtin_type) && dictionary->get_typed_value_class_name() == value_native_type && dictionary->get_typed_value_script() == *value_script_type;
}
*dst = result;
ip += 9;
}
DISPATCH_OPCODE;
OPCODE(OPCODE_TYPE_TEST_NATIVE) {
CHECK_SPACE(4);
@ -1384,6 +1442,50 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
}
DISPATCH_OPCODE;
OPCODE(OPCODE_ASSIGN_TYPED_DICTIONARY) {
CHECK_SPACE(9);
GET_VARIANT_PTR(dst, 0);
GET_VARIANT_PTR(src, 1);
GET_VARIANT_PTR(key_script_type, 2);
Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5];
int key_native_type_idx = _code_ptr[ip + 6];
GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
const StringName key_native_type = _global_names_ptr[key_native_type_idx];
GET_VARIANT_PTR(value_script_type, 3);
Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7];
int value_native_type_idx = _code_ptr[ip + 8];
GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
const StringName value_native_type = _global_names_ptr[value_native_type_idx];
if (src->get_type() != Variant::DICTIONARY) {
#ifdef DEBUG_ENABLED
err_text = vformat(R"(Trying to assign a value of type "%s" to a variable of type "Dictionary[%s, %s]".)",
_get_var_type(src), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
_get_element_type(value_builtin_type, value_native_type, *value_script_type));
#endif // DEBUG_ENABLED
OPCODE_BREAK;
}
Dictionary *dictionary = VariantInternal::get_dictionary(src);
if (dictionary->get_typed_key_builtin() != ((uint32_t)key_builtin_type) || dictionary->get_typed_key_class_name() != key_native_type || dictionary->get_typed_key_script() != *key_script_type ||
dictionary->get_typed_value_builtin() != ((uint32_t)value_builtin_type) || dictionary->get_typed_value_class_name() != value_native_type || dictionary->get_typed_value_script() != *value_script_type) {
#ifdef DEBUG_ENABLED
err_text = vformat(R"(Trying to assign a dictionary of type "%s" to a variable of type "Dictionary[%s, %s]".)",
_get_var_type(src), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
_get_element_type(value_builtin_type, value_native_type, *value_script_type));
#endif // DEBUG_ENABLED
OPCODE_BREAK;
}
*dst = *src;
ip += 9;
}
DISPATCH_OPCODE;
OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) {
CHECK_SPACE(4);
GET_VARIANT_PTR(dst, 0);
@ -1703,12 +1805,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
GET_INSTRUCTION_ARG(dst, argc * 2);
*dst = Variant(); // Clear potential previous typed dictionary.
*dst = dict;
ip += 2;
}
DISPATCH_OPCODE;
OPCODE(OPCODE_CONSTRUCT_TYPED_DICTIONARY) {
LOAD_INSTRUCTION_ARGS
CHECK_SPACE(6 + instr_arg_count);
ip += instr_arg_count;
int argc = _code_ptr[ip + 1];
GET_INSTRUCTION_ARG(key_script_type, argc * 2 + 1);
Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 2];
int key_native_type_idx = _code_ptr[ip + 3];
GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
const StringName key_native_type = _global_names_ptr[key_native_type_idx];
GET_INSTRUCTION_ARG(value_script_type, argc * 2 + 2);
Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 4];
int value_native_type_idx = _code_ptr[ip + 5];
GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
const StringName value_native_type = _global_names_ptr[value_native_type_idx];
Dictionary dict;
for (int i = 0; i < argc; i++) {
GET_INSTRUCTION_ARG(k, i * 2 + 0);
GET_INSTRUCTION_ARG(v, i * 2 + 1);
dict[*k] = *v;
}
GET_INSTRUCTION_ARG(dst, argc * 2);
*dst = Variant(); // Clear potential previous typed dictionary.
*dst = Dictionary(dict, key_builtin_type, key_native_type, *key_script_type, value_builtin_type, value_native_type, *value_script_type);
ip += 6;
}
DISPATCH_OPCODE;
OPCODE(OPCODE_CALL_ASYNC)
OPCODE(OPCODE_CALL_RETURN)
OPCODE(OPCODE_CALL) {
@ -2651,6 +2792,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
retvalue = *array;
#ifdef DEBUG_ENABLED
exit_ok = true;
#endif // DEBUG_ENABLED
OPCODE_BREAK;
}
OPCODE(OPCODE_RETURN_TYPED_DICTIONARY) {
CHECK_SPACE(8);
GET_VARIANT_PTR(r, 0);
GET_VARIANT_PTR(key_script_type, 1);
Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 4];
int key_native_type_idx = _code_ptr[ip + 5];
GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count);
const StringName key_native_type = _global_names_ptr[key_native_type_idx];
GET_VARIANT_PTR(value_script_type, 2);
Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 6];
int value_native_type_idx = _code_ptr[ip + 7];
GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count);
const StringName value_native_type = _global_names_ptr[value_native_type_idx];
if (r->get_type() != Variant::DICTIONARY) {
#ifdef DEBUG_ENABLED
err_text = vformat(R"(Trying to return a value of type "%s" where expected return type is "Dictionary[%s, %s]".)",
_get_var_type(r), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
_get_element_type(value_builtin_type, value_native_type, *value_script_type));
#endif // DEBUG_ENABLED
OPCODE_BREAK;
}
Dictionary *dictionary = VariantInternal::get_dictionary(r);
if (dictionary->get_typed_key_builtin() != ((uint32_t)key_builtin_type) || dictionary->get_typed_key_class_name() != key_native_type || dictionary->get_typed_key_script() != *key_script_type ||
dictionary->get_typed_value_builtin() != ((uint32_t)value_builtin_type) || dictionary->get_typed_value_class_name() != value_native_type || dictionary->get_typed_value_script() != *value_script_type) {
#ifdef DEBUG_ENABLED
err_text = vformat(R"(Trying to return a dictionary of type "%s" where expected return type is "Dictionary[%s, %s]".)",
_get_var_type(r), _get_element_type(key_builtin_type, key_native_type, *key_script_type),
_get_element_type(value_builtin_type, value_native_type, *value_script_type));
#endif // DEBUG_ENABLED
OPCODE_BREAK;
}
retvalue = *dictionary;
#ifdef DEBUG_ENABLED
exit_ok = true;
#endif // DEBUG_ENABLED

View File

@ -0,0 +1,3 @@
func test():
for key: int in { "a": 1 }:
print(key)

View File

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot include a value of type "String" as "int".

View File

@ -0,0 +1,4 @@
func test():
var differently: Dictionary[float, float] = { 1.0: 0.0 }
var typed: Dictionary[int, int] = differently
print('not ok')

View File

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot assign a value of type Dictionary[float, float] to variable "typed" with specified type Dictionary[int, int].

View File

@ -0,0 +1,2 @@
func test():
const dict: Dictionary[int, int] = { "Hello": "World" }

View File

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot include a value of type "String" as "int".

View File

@ -0,0 +1,4 @@
func test():
var unconvertible := 1
var typed: Dictionary[Object, Object] = { unconvertible: unconvertible }
print('not ok')

View File

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Cannot have a key of type "int" in a dictionary of type "Dictionary[Object, Object]".

View File

@ -0,0 +1,7 @@
func expect_typed(typed: Dictionary[int, int]):
print(typed.size())
func test():
var differently: Dictionary[float, float] = { 1.0: 0.0 }
expect_typed(differently)
print('not ok')

View File

@ -0,0 +1,2 @@
GDTEST_ANALYZER_ERROR
Invalid argument for "expect_typed()" function: argument 1 should be "Dictionary[int, int]" but is "Dictionary[float, float]".

View File

@ -0,0 +1,20 @@
func print_untyped(dictionary = { 0: 1 }) -> void:
print(dictionary)
print(dictionary.get_typed_key_builtin())
print(dictionary.get_typed_value_builtin())
func print_inferred(dictionary := { 2: 3 }) -> void:
print(dictionary)
print(dictionary.get_typed_key_builtin())
print(dictionary.get_typed_value_builtin())
func print_typed(dictionary: Dictionary[int, int] = { 4: 5 }) -> void:
print(dictionary)
print(dictionary.get_typed_key_builtin())
print(dictionary.get_typed_value_builtin())
func test():
print_untyped()
print_inferred()
print_typed()
print('ok')

View File

@ -0,0 +1,11 @@
GDTEST_OK
{ 0: 1 }
0
0
{ 2: 3 }
0
0
{ 4: 5 }
2
2
ok

View File

@ -0,0 +1,4 @@
func test():
var dict := { 0: 0 }
dict[0] = 1
print(dict[0])

View File

@ -0,0 +1,214 @@
class A: pass
class B extends A: pass
enum E { E0 = 391, E1 = 193 }
func floats_identity(floats: Dictionary[float, float]): return floats
class Members:
var one: Dictionary[int, int] = { 104: 401 }
var two: Dictionary[int, int] = one
func check_passing() -> bool:
Utils.check(str(one) == '{ 104: 401 }')
Utils.check(str(two) == '{ 104: 401 }')
two[582] = 285
Utils.check(str(one) == '{ 104: 401, 582: 285 }')
Utils.check(str(two) == '{ 104: 401, 582: 285 }')
two = { 486: 684 }
Utils.check(str(one) == '{ 104: 401, 582: 285 }')
Utils.check(str(two) == '{ 486: 684 }')
return true
@warning_ignore("unsafe_method_access")
@warning_ignore("assert_always_true")
@warning_ignore("return_value_discarded")
func test():
var untyped_basic = { 459: 954 }
Utils.check(str(untyped_basic) == '{ 459: 954 }')
Utils.check(untyped_basic.get_typed_key_builtin() == TYPE_NIL)
Utils.check(untyped_basic.get_typed_value_builtin() == TYPE_NIL)
var inferred_basic := { 366: 663 }
Utils.check(str(inferred_basic) == '{ 366: 663 }')
Utils.check(inferred_basic.get_typed_key_builtin() == TYPE_NIL)
Utils.check(inferred_basic.get_typed_value_builtin() == TYPE_NIL)
var typed_basic: Dictionary = { 521: 125 }
Utils.check(str(typed_basic) == '{ 521: 125 }')
Utils.check(typed_basic.get_typed_key_builtin() == TYPE_NIL)
Utils.check(typed_basic.get_typed_value_builtin() == TYPE_NIL)
var empty_floats: Dictionary[float, float] = {}
Utils.check(str(empty_floats) == '{ }')
Utils.check(empty_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(empty_floats.get_typed_value_builtin() == TYPE_FLOAT)
untyped_basic = empty_floats
Utils.check(untyped_basic.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(untyped_basic.get_typed_value_builtin() == TYPE_FLOAT)
inferred_basic = empty_floats
Utils.check(inferred_basic.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(inferred_basic.get_typed_value_builtin() == TYPE_FLOAT)
typed_basic = empty_floats
Utils.check(typed_basic.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(typed_basic.get_typed_value_builtin() == TYPE_FLOAT)
empty_floats[705.0] = 507.0
untyped_basic[430.0] = 34.0
inferred_basic[263.0] = 362.0
typed_basic[518.0] = 815.0
Utils.check(str(empty_floats) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
Utils.check(str(untyped_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
Utils.check(str(inferred_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
Utils.check(str(typed_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }')
const constant_float := 950.0
const constant_int := 170
var typed_float := 954.0
var filled_floats: Dictionary[float, float] = { constant_float: constant_int, typed_float: empty_floats[430.0] + empty_floats[263.0] }
Utils.check(str(filled_floats) == '{ 950: 170, 954: 396 }')
Utils.check(filled_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(filled_floats.get_typed_value_builtin() == TYPE_FLOAT)
var casted_floats := { empty_floats[263.0] * 2: empty_floats[263.0] / 2 } as Dictionary[float, float]
Utils.check(str(casted_floats) == '{ 724: 181 }')
Utils.check(casted_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(casted_floats.get_typed_value_builtin() == TYPE_FLOAT)
var returned_floats = (func () -> Dictionary[float, float]: return { 554: 455 }).call()
Utils.check(str(returned_floats) == '{ 554: 455 }')
Utils.check(returned_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(returned_floats.get_typed_value_builtin() == TYPE_FLOAT)
var passed_floats = floats_identity({ 663.0 if randf() > 0.5 else 663.0: 366.0 if randf() <= 0.5 else 366.0 })
Utils.check(str(passed_floats) == '{ 663: 366 }')
Utils.check(passed_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(passed_floats.get_typed_value_builtin() == TYPE_FLOAT)
var default_floats = (func (floats: Dictionary[float, float] = { 364.0: 463.0 }): return floats).call()
Utils.check(str(default_floats) == '{ 364: 463 }')
Utils.check(default_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(default_floats.get_typed_value_builtin() == TYPE_FLOAT)
var typed_int := 556
var converted_floats: Dictionary[float, float] = { typed_int: typed_int }
converted_floats[498.0] = 894
Utils.check(str(converted_floats) == '{ 556: 556, 498: 894 }')
Utils.check(converted_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(converted_floats.get_typed_value_builtin() == TYPE_FLOAT)
const constant_basic = { 228: 822 }
Utils.check(str(constant_basic) == '{ 228: 822 }')
Utils.check(constant_basic.get_typed_key_builtin() == TYPE_NIL)
Utils.check(constant_basic.get_typed_value_builtin() == TYPE_NIL)
const constant_floats: Dictionary[float, float] = { constant_float - constant_basic[228] - constant_int: constant_float + constant_basic[228] + constant_int }
Utils.check(str(constant_floats) == '{ -42: 1942 }')
Utils.check(constant_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(constant_floats.get_typed_value_builtin() == TYPE_FLOAT)
var source_floats: Dictionary[float, float] = { 999.74: 47.999 }
untyped_basic = source_floats
var destination_floats: Dictionary[float, float] = untyped_basic
destination_floats[999.74] -= 0.999
Utils.check(str(source_floats) == '{ 999.74: 47 }')
Utils.check(str(untyped_basic) == '{ 999.74: 47 }')
Utils.check(str(destination_floats) == '{ 999.74: 47 }')
Utils.check(destination_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(destination_floats.get_typed_value_builtin() == TYPE_FLOAT)
var duplicated_floats := empty_floats.duplicate()
duplicated_floats.erase(705.0)
duplicated_floats.erase(430.0)
duplicated_floats.erase(518.0)
duplicated_floats[263.0] *= 3
Utils.check(str(duplicated_floats) == '{ 263: 1086 }')
Utils.check(duplicated_floats.get_typed_key_builtin() == TYPE_FLOAT)
Utils.check(duplicated_floats.get_typed_value_builtin() == TYPE_FLOAT)
var b_objects: Dictionary[int, B] = { 0: B.new(), 1: B.new() as A, 2: null }
Utils.check(b_objects.size() == 3)
Utils.check(b_objects.get_typed_value_builtin() == TYPE_OBJECT)
Utils.check(b_objects.get_typed_value_script() == B)
var a_objects: Dictionary[int, A] = { 0: A.new(), 1: B.new(), 2: null, 3: b_objects[0] }
Utils.check(a_objects.size() == 4)
Utils.check(a_objects.get_typed_value_builtin() == TYPE_OBJECT)
Utils.check(a_objects.get_typed_value_script() == A)
var a_passed = (func check_a_passing(p_objects: Dictionary[int, A]): return p_objects.size()).call(a_objects)
Utils.check(a_passed == 4)
var b_passed = (func check_b_passing(basic: Dictionary): return basic[0] != null).call(b_objects)
Utils.check(b_passed == true)
var empty_strings: Dictionary[String, String] = {}
var empty_bools: Dictionary[bool, bool] = {}
var empty_basic_one := {}
var empty_basic_two := {}
Utils.check(empty_strings == empty_bools)
Utils.check(empty_basic_one == empty_basic_two)
Utils.check(empty_strings.hash() == empty_bools.hash())
Utils.check(empty_basic_one.hash() == empty_basic_two.hash())
var assign_source: Dictionary[int, int] = { 527: 725 }
var assign_target: Dictionary[int, int] = {}
assign_target.assign(assign_source)
Utils.check(str(assign_source) == '{ 527: 725 }')
Utils.check(str(assign_target) == '{ 527: 725 }')
assign_source[657] = 756
Utils.check(str(assign_source) == '{ 527: 725, 657: 756 }')
Utils.check(str(assign_target) == '{ 527: 725 }')
var defaults_passed = (func check_defaults_passing(one: Dictionary[int, int] = {}, two := one):
one[887] = 788
two[198] = 891
Utils.check(str(one) == '{ 887: 788, 198: 891 }')
Utils.check(str(two) == '{ 887: 788, 198: 891 }')
two = {130: 31}
Utils.check(str(one) == '{ 887: 788, 198: 891 }')
Utils.check(str(two) == '{ 130: 31 }')
return true
).call()
Utils.check(defaults_passed == true)
var members := Members.new()
var members_passed := members.check_passing()
Utils.check(members_passed == true)
var typed_enums: Dictionary[E, E] = {}
typed_enums[E.E0] = E.E1
Utils.check(str(typed_enums) == '{ 391: 193 }')
Utils.check(typed_enums.get_typed_key_builtin() == TYPE_INT)
Utils.check(typed_enums.get_typed_value_builtin() == TYPE_INT)
const const_enums: Dictionary[E, E] = {}
Utils.check(const_enums.get_typed_key_builtin() == TYPE_INT)
Utils.check(const_enums.get_typed_key_class_name() == &'')
Utils.check(const_enums.get_typed_value_builtin() == TYPE_INT)
Utils.check(const_enums.get_typed_value_class_name() == &'')
var a := A.new()
var b := B.new()
var typed_natives: Dictionary[RefCounted, RefCounted] = { a: b }
var typed_scripts = Dictionary(typed_natives, TYPE_OBJECT, "RefCounted", A, TYPE_OBJECT, "RefCounted", B)
Utils.check(typed_scripts[a] == b)
print('ok')

View File

@ -0,0 +1,2 @@
GDTEST_OK
ok

View File

@ -0,0 +1,9 @@
class Inner:
var prop = "Inner"
var dict: Dictionary[int, Inner] = { 0: Inner.new() }
func test():
var element: Inner = dict[0]
print(element.prop)

View File

@ -0,0 +1,2 @@
GDTEST_OK
Inner

View File

@ -0,0 +1,5 @@
func test():
var my_dictionary: Dictionary[int, String] = { 1: "one", 2: "two", 3: "three" }
var inferred_dictionary := { 1: "one", 2: "two", 3: "three" } # This is Dictionary[int, String].
print(my_dictionary)
print(inferred_dictionary)

View File

@ -0,0 +1,3 @@
GDTEST_OK
{ 1: "one", 2: "two", 3: "three" }
{ 1: "one", 2: "two", 3: "three" }

View File

@ -0,0 +1,4 @@
func test():
var basic := { 1: 1 }
var typed: Dictionary[int, int] = basic
print('not ok')

View File

@ -0,0 +1,6 @@
GDTEST_RUNTIME_ERROR
>> SCRIPT ERROR
>> on function: test()
>> runtime/errors/typed_dictionary_assign_basic_to_typed.gd
>> 3
>> Trying to assign a dictionary of type "Dictionary" to a variable of type "Dictionary[int, int]".

View File

@ -0,0 +1,4 @@
func test():
var differently: Variant = { 1.0: 0.0 } as Dictionary[float, float]
var typed: Dictionary[int, int] = differently
print('not ok')

View File

@ -0,0 +1,6 @@
GDTEST_RUNTIME_ERROR
>> SCRIPT ERROR
>> on function: test()
>> runtime/errors/typed_dictionary_assign_differently_typed.gd
>> 3
>> Trying to assign a dictionary of type "Dictionary[float, float]" to a variable of type "Dictionary[int, int]".

View File

@ -0,0 +1,7 @@
class Foo: pass
class Bar extends Foo: pass
class Baz extends Foo: pass
func test():
var typed: Dictionary[Bar, Bar] = { Baz.new() as Foo: Baz.new() as Foo }
print('not ok')

View File

@ -0,0 +1,5 @@
GDTEST_RUNTIME_ERROR
>> ERROR
>> Method/function failed.
>> Unable to convert key from "Object" to "Object".
not ok

View File

@ -0,0 +1,7 @@
func expect_typed(typed: Dictionary[int, int]):
print(typed.size())
func test():
var basic := { 1: 1 }
expect_typed(basic)
print('not ok')

View File

@ -0,0 +1,6 @@
GDTEST_RUNTIME_ERROR
>> SCRIPT ERROR
>> on function: test()
>> runtime/errors/typed_dictionary_pass_basic_to_typed.gd
>> 6
>> Invalid type in function 'expect_typed' in base 'RefCounted (typed_dictionary_pass_basic_to_typed.gd)'. The dictionary of argument 1 (Dictionary) does not have the same element type as the expected typed dictionary argument.

View File

@ -0,0 +1,7 @@
func expect_typed(typed: Dictionary[int, int]):
print(typed.size())
func test():
var differently: Variant = { 1.0: 0.0 } as Dictionary[float, float]
expect_typed(differently)
print('not ok')

View File

@ -0,0 +1,6 @@
GDTEST_RUNTIME_ERROR
>> SCRIPT ERROR
>> on function: test()
>> runtime/errors/typed_dictionary_pass_differently_to_typed.gd
>> 6
>> Invalid type in function 'expect_typed' in base 'RefCounted (typed_dictionary_pass_differently_to_typed.gd)'. The dictionary of argument 1 (Dictionary[float, float]) does not have the same element type as the expected typed dictionary argument.

View File

@ -28,13 +28,18 @@ func test():
prints(var_to_str(e), var_to_str(elem))
print("Test String-keys dictionary.")
var d1 := {a = 1, b = 2, c = 3}
var d1 := { a = 1, b = 2, c = 3 }
for k: StringName in d1:
var key := k
prints(var_to_str(k), var_to_str(key))
print("Test RefCounted-keys dictionary.")
var d2 := {RefCounted.new(): 1, Resource.new(): 2, ConfigFile.new(): 3}
var d2 := { RefCounted.new(): 1, Resource.new(): 2, ConfigFile.new(): 3 }
for k: RefCounted in d2:
var key := k
prints(k.get_class(), key.get_class())
print("Test implicitly typed dictionary literal.")
for k: StringName in { x = 123, y = 456, z = 789 }:
var key := k
prints(var_to_str(k), var_to_str(key))

View File

@ -27,3 +27,7 @@ Test RefCounted-keys dictionary.
RefCounted RefCounted
Resource Resource
ConfigFile ConfigFile
Test implicitly typed dictionary literal.
&"x" &"x"
&"y" &"y"
&"z" &"z"

View File

@ -31,6 +31,16 @@ var test_var_hard_array_my_enum: Array[MyEnum]
var test_var_hard_array_resource: Array[Resource]
var test_var_hard_array_this: Array[TestMemberInfo]
var test_var_hard_array_my_class: Array[MyClass]
var test_var_hard_dictionary: Dictionary
var test_var_hard_dictionary_int_variant: Dictionary[int, Variant]
var test_var_hard_dictionary_variant_int: Dictionary[Variant, int]
var test_var_hard_dictionary_int_int: Dictionary[int, int]
var test_var_hard_dictionary_variant_type: Dictionary[Variant.Type, Variant.Type]
var test_var_hard_dictionary_node_process_mode: Dictionary[Node.ProcessMode, Node.ProcessMode]
var test_var_hard_dictionary_my_enum: Dictionary[MyEnum, MyEnum]
var test_var_hard_dictionary_resource: Dictionary[Resource, Resource]
var test_var_hard_dictionary_this: Dictionary[TestMemberInfo, TestMemberInfo]
var test_var_hard_dictionary_my_class: Dictionary[MyClass, MyClass]
var test_var_hard_resource: Resource
var test_var_hard_this: TestMemberInfo
var test_var_hard_my_class: MyClass
@ -43,17 +53,17 @@ func test_func_weak_null(): return null
func test_func_weak_int(): return 1
func test_func_hard_variant() -> Variant: return null
func test_func_hard_int() -> int: return 1
func test_func_args_1(_a: int, _b: Array[int], _c: int = 1, _d = 2): pass
func test_func_args_1(_a: int, _b: Array[int], _c: Dictionary[int, int], _d: int = 1, _e = 2): pass
func test_func_args_2(_a = 1, _b = _a, _c = [2], _d = 3): pass
signal test_signal_1()
signal test_signal_2(a: Variant, b)
signal test_signal_3(a: int, b: Array[int])
signal test_signal_4(a: Variant.Type, b: Array[Variant.Type])
signal test_signal_5(a: MyEnum, b: Array[MyEnum])
signal test_signal_6(a: Resource, b: Array[Resource])
signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo])
signal test_signal_8(a: MyClass, b: Array[MyClass])
signal test_signal_3(a: int, b: Array[int], c: Dictionary[int, int])
signal test_signal_4(a: Variant.Type, b: Array[Variant.Type], c: Dictionary[Variant.Type, Variant.Type])
signal test_signal_5(a: MyEnum, b: Array[MyEnum], c: Dictionary[MyEnum, MyEnum])
signal test_signal_6(a: Resource, b: Array[Resource], c: Dictionary[Resource, Resource])
signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo], c: Dictionary[TestMemberInfo, TestMemberInfo])
signal test_signal_8(a: MyClass, b: Array[MyClass], c: Dictionary[MyClass, MyClass])
func no_exec():
test_signal_1.emit()

View File

@ -23,6 +23,16 @@ var test_var_hard_array_my_enum: Array[TestMemberInfo.MyEnum]
var test_var_hard_array_resource: Array[Resource]
var test_var_hard_array_this: Array[TestMemberInfo]
var test_var_hard_array_my_class: Array[RefCounted]
var test_var_hard_dictionary: Dictionary
var test_var_hard_dictionary_int_variant: Dictionary[int, Variant]
var test_var_hard_dictionary_variant_int: Dictionary[Variant, int]
var test_var_hard_dictionary_int_int: Dictionary[int, int]
var test_var_hard_dictionary_variant_type: Dictionary[Variant.Type, Variant.Type]
var test_var_hard_dictionary_node_process_mode: Dictionary[Node.ProcessMode, Node.ProcessMode]
var test_var_hard_dictionary_my_enum: Dictionary[TestMemberInfo.MyEnum, TestMemberInfo.MyEnum]
var test_var_hard_dictionary_resource: Dictionary[Resource, Resource]
var test_var_hard_dictionary_this: Dictionary[TestMemberInfo, TestMemberInfo]
var test_var_hard_dictionary_my_class: Dictionary[RefCounted, RefCounted]
var test_var_hard_resource: Resource
var test_var_hard_this: TestMemberInfo
var test_var_hard_my_class: RefCounted
@ -33,13 +43,13 @@ func test_func_weak_null() -> Variant
func test_func_weak_int() -> Variant
func test_func_hard_variant() -> Variant
func test_func_hard_int() -> int
func test_func_args_1(_a: int, _b: Array[int], _c: int = 1, _d: Variant = 2) -> void
func test_func_args_1(_a: int, _b: Array[int], _c: Dictionary[int, int], _d: int = 1, _e: Variant = 2) -> void
func test_func_args_2(_a: Variant = 1, _b: Variant = null, _c: Variant = null, _d: Variant = 3) -> void
signal test_signal_1()
signal test_signal_2(a: Variant, b: Variant)
signal test_signal_3(a: int, b: Array[int])
signal test_signal_4(a: Variant.Type, b: Array[Variant.Type])
signal test_signal_5(a: TestMemberInfo.MyEnum, b: Array[TestMemberInfo.MyEnum])
signal test_signal_6(a: Resource, b: Array[Resource])
signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo])
signal test_signal_8(a: RefCounted, b: Array[RefCounted])
signal test_signal_3(a: int, b: Array[int], c: Dictionary[int, int])
signal test_signal_4(a: Variant.Type, b: Array[Variant.Type], c: Dictionary[Variant.Type, Variant.Type])
signal test_signal_5(a: TestMemberInfo.MyEnum, b: Array[TestMemberInfo.MyEnum], c: Dictionary[TestMemberInfo.MyEnum, TestMemberInfo.MyEnum])
signal test_signal_6(a: Resource, b: Array[Resource], c: Dictionary[Resource, Resource])
signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo], c: Dictionary[TestMemberInfo, TestMemberInfo])
signal test_signal_8(a: RefCounted, b: Array[RefCounted], c: Dictionary[RefCounted, RefCounted])

View File

@ -0,0 +1,6 @@
func test_param(dictionary: Dictionary[int, String]) -> void:
print(dictionary.get_typed_key_builtin() == TYPE_INT)
print(dictionary.get_typed_value_builtin() == TYPE_STRING)
func test() -> void:
test_param({ 123: "some_string" })

View File

@ -0,0 +1,3 @@
GDTEST_OK
true
true

View File

@ -0,0 +1,7 @@
func test():
var untyped: Variant = 32
var typed: Dictionary[int, int] = { untyped: untyped }
Utils.check(typed.get_typed_key_builtin() == TYPE_INT)
Utils.check(typed.get_typed_value_builtin() == TYPE_INT)
Utils.check(str(typed) == '{ 32: 32 }')
print('ok')

View File

@ -24,6 +24,11 @@ static func get_type(property: Dictionary, is_return: bool = false) -> String:
if str(property.hint_string).is_empty():
return "Array[<unknown type>]"
return "Array[%s]" % property.hint_string
TYPE_DICTIONARY:
if property.hint == PROPERTY_HINT_DICTIONARY_TYPE:
if str(property.hint_string).is_empty():
return "Dictionary[<unknown type>, <unknown type>]"
return "Dictionary[%s]" % str(property.hint_string).replace(";", ", ")
TYPE_OBJECT:
if not str(property.class_name).is_empty():
return property.class_name
@ -188,6 +193,8 @@ static func get_property_hint_name(hint: PropertyHint) -> String:
return "PROPERTY_HINT_INT_IS_POINTER"
PROPERTY_HINT_ARRAY_TYPE:
return "PROPERTY_HINT_ARRAY_TYPE"
PROPERTY_HINT_DICTIONARY_TYPE:
return "PROPERTY_HINT_DICTIONARY_TYPE"
PROPERTY_HINT_LOCALE_ID:
return "PROPERTY_HINT_LOCALE_ID"
PROPERTY_HINT_LOCALIZABLE_STRING:

View File

@ -807,7 +807,7 @@ partial class ExportedFields
properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@_fieldRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)38, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)31, name: PropertyName.@_fieldEmptyInt64Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
return properties;

View File

@ -925,7 +925,7 @@ partial class ExportedProperties
properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@PropertyRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)38, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true));
return properties;
}

View File

@ -88,7 +88,8 @@ namespace Godot.SourceGenerators
HideQuaternionEdit = 35,
Password = 36,
LayersAvoidance = 37,
Max = 38
DictionaryType = 38,
Max = 39
}
[Flags]

View File

@ -274,6 +274,14 @@ namespace Godot.SourceGenerators
return null;
}
public static ITypeSymbol[]? GetGenericElementTypes(ITypeSymbol typeSymbol)
{
if (typeSymbol is INamedTypeSymbol { IsGenericType: true } genericType)
return genericType.TypeArguments.ToArray();
return null;
}
private static StringBuilder Append(this StringBuilder source, string a, string b)
=> source.Append(a).Append(b);

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
@ -728,8 +729,72 @@ namespace Godot.SourceGenerators
if (!isTypeArgument && variantType == VariantType.Dictionary)
{
// TODO: Dictionaries are not supported in the inspector
return false;
var elementTypes = MarshalUtils.GetGenericElementTypes(type);
if (elementTypes == null)
return false; // Non-generic Dictionary, so there's no hint to add
Debug.Assert(elementTypes.Length == 2);
var keyElementMarshalType = MarshalUtils.ConvertManagedTypeToMarshalType(elementTypes[0], typeCache)!.Value;
var keyElementVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(keyElementMarshalType)!.Value;
var keyIsPresetHint = false;
var keyHintString = (string?)null;
if (keyElementVariantType == VariantType.String || keyElementVariantType == VariantType.StringName)
keyIsPresetHint = GetStringArrayEnumHint(keyElementVariantType, exportAttr, out keyHintString);
if (!keyIsPresetHint)
{
bool hintRes = TryGetMemberExportHint(typeCache, elementTypes[0],
exportAttr, keyElementVariantType, isTypeArgument: true,
out var keyElementHint, out var keyElementHintString);
// Format: type/hint:hint_string
if (hintRes)
{
keyHintString = (int)keyElementVariantType + "/" + (int)keyElementHint + ":";
if (keyElementHintString != null)
keyHintString += keyElementHintString;
}
else
{
keyHintString = (int)keyElementVariantType + "/" + (int)PropertyHint.None + ":";
}
}
var valueElementMarshalType = MarshalUtils.ConvertManagedTypeToMarshalType(elementTypes[1], typeCache)!.Value;
var valueElementVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(valueElementMarshalType)!.Value;
var valueIsPresetHint = false;
var valueHintString = (string?)null;
if (valueElementVariantType == VariantType.String || valueElementVariantType == VariantType.StringName)
valueIsPresetHint = GetStringArrayEnumHint(valueElementVariantType, exportAttr, out valueHintString);
if (!valueIsPresetHint)
{
bool hintRes = TryGetMemberExportHint(typeCache, elementTypes[1],
exportAttr, valueElementVariantType, isTypeArgument: true,
out var valueElementHint, out var valueElementHintString);
// Format: type/hint:hint_string
if (hintRes)
{
valueHintString = (int)valueElementVariantType + "/" + (int)valueElementHint + ":";
if (valueElementHintString != null)
valueHintString += valueElementHintString;
}
else
{
valueHintString = (int)valueElementVariantType + "/" + (int)PropertyHint.None + ":";
}
}
hint = PropertyHint.DictionaryType;
hintString = keyHintString != null && valueHintString != null ? $"{keyHintString};{valueHintString}" : null;
return hintString != null;
}
return false;

View File

@ -3809,6 +3809,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
} else if (return_info.type == Variant::ARRAY && return_info.hint == PROPERTY_HINT_ARRAY_TYPE) {
imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic";
imethod.return_type.generic_type_parameters.push_back(TypeReference(return_info.hint_string));
} else if (return_info.type == Variant::DICTIONARY && return_info.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic";
Vector<String> split = return_info.hint_string.split(";");
imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(0)));
imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(1)));
} else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
imethod.return_type.cname = return_info.hint_string;
} else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
@ -3836,6 +3841,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
} else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string));
} else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
Vector<String> split = arginfo.hint_string.split(";");
iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0)));
iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1)));
} else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
iarg.type.cname = arginfo.hint_string;
} else if (arginfo.type == Variant::NIL) {
@ -3963,6 +3973,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
} else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) {
iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string));
} else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) {
iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic";
Vector<String> split = arginfo.hint_string.split(";");
iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0)));
iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1)));
} else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
iarg.type.cname = arginfo.hint_string;
} else if (arginfo.type == Variant::NIL) {

View File

@ -369,6 +369,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
value = make_local_resource(value, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
}
}
if (value.get_type() == Variant::ARRAY) {
Array set_array = value;
value = setup_resources_in_array(set_array, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
@ -383,25 +384,20 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
}
}
}
if (value.get_type() == Variant::DICTIONARY) {
Dictionary dictionary = value;
const Array keys = dictionary.keys();
const Array values = dictionary.values();
Dictionary set_dict = value;
value = setup_resources_in_dictionary(set_dict, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
if (has_local_resource(values) || has_local_resource(keys)) {
Array duplicated_keys = keys.duplicate(true);
Array duplicated_values = values.duplicate(true);
bool is_get_valid = false;
Variant get_value = node->get(snames[nprops[j].name], &is_get_valid);
duplicated_keys = setup_resources_in_array(duplicated_keys, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
duplicated_values = setup_resources_in_array(duplicated_values, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state);
dictionary.clear();
for (int dictionary_index = 0; dictionary_index < keys.size(); dictionary_index++) {
dictionary[duplicated_keys[dictionary_index]] = duplicated_values[dictionary_index];
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());
}
value = dictionary;
}
}
@ -539,6 +535,30 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
array.set(i, dnp.base->get_node_or_null(paths[i]));
}
dnp.base->set(dnp.property, array);
} else if (dnp.value.get_type() == Variant::DICTIONARY) {
Dictionary paths = dnp.value;
bool valid;
Dictionary dict = dnp.base->get(dnp.property, &valid);
ERR_CONTINUE(!valid);
dict = dict.duplicate();
bool convert_key = dict.get_typed_key_builtin() == Variant::OBJECT &&
ClassDB::is_parent_class(dict.get_typed_key_class_name(), "Node");
bool convert_value = dict.get_typed_value_builtin() == Variant::OBJECT &&
ClassDB::is_parent_class(dict.get_typed_value_class_name(), "Node");
for (int i = 0; i < paths.size(); i++) {
Variant key = paths.get_key_at_index(i);
if (convert_key) {
key = dnp.base->get_node_or_null(key);
}
Variant value = paths.get_value_at_index(i);
if (convert_value) {
value = dnp.base->get_node_or_null(value);
}
dict[key] = value;
}
dnp.base->set(dnp.property, dict);
} else {
dnp.base->set(dnp.property, dnp.base->get_node_or_null(dnp.value));
}
@ -641,6 +661,26 @@ Array SceneState::setup_resources_in_array(Array &p_array_to_scan, const SceneSt
return p_array_to_scan;
}
Dictionary SceneState::setup_resources_in_dictionary(Dictionary &p_dictionary_to_scan, const SceneState::NodeData &p_n, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const {
const Array keys = p_dictionary_to_scan.keys();
const Array values = p_dictionary_to_scan.values();
if (has_local_resource(values) || has_local_resource(keys)) {
Array duplicated_keys = keys.duplicate(true);
Array duplicated_values = values.duplicate(true);
duplicated_keys = setup_resources_in_array(duplicated_keys, p_n, p_resources_local_to_sub_scene, p_node, p_sname, p_resources_local_to_scene, p_i, p_ret_nodes, p_edit_state);
duplicated_values = setup_resources_in_array(duplicated_values, p_n, p_resources_local_to_sub_scene, p_node, p_sname, p_resources_local_to_scene, p_i, p_ret_nodes, p_edit_state);
p_dictionary_to_scan.clear();
for (int i = 0; i < keys.size(); i++) {
p_dictionary_to_scan[duplicated_keys[i]] = duplicated_values[i];
}
}
return p_dictionary_to_scan;
}
bool SceneState::has_local_resource(const Array &p_array) const {
for (int i = 0; i < p_array.size(); i++) {
Ref<Resource> res = p_array[i];
@ -810,6 +850,53 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has
value = new_array;
}
}
} else if (E.type == Variant::DICTIONARY && E.hint == PROPERTY_HINT_TYPE_STRING) {
int key_value_separator = E.hint_string.find(";");
if (key_value_separator >= 0) {
int key_subtype_separator = E.hint_string.find(":");
String key_subtype_string = E.hint_string.substr(0, key_subtype_separator);
int key_slash_pos = key_subtype_string.find("/");
PropertyHint key_subtype_hint = PropertyHint::PROPERTY_HINT_NONE;
if (key_slash_pos >= 0) {
key_subtype_hint = PropertyHint(key_subtype_string.get_slice("/", 1).to_int());
key_subtype_string = key_subtype_string.substr(0, key_slash_pos);
}
Variant::Type key_subtype = Variant::Type(key_subtype_string.to_int());
bool convert_key = key_subtype == Variant::OBJECT && key_subtype_hint == PROPERTY_HINT_NODE_TYPE;
int value_subtype_separator = E.hint_string.find(":", key_value_separator) - (key_value_separator + 1);
String value_subtype_string = E.hint_string.substr(key_value_separator + 1, value_subtype_separator);
int value_slash_pos = value_subtype_string.find("/");
PropertyHint value_subtype_hint = PropertyHint::PROPERTY_HINT_NONE;
if (value_slash_pos >= 0) {
value_subtype_hint = PropertyHint(value_subtype_string.get_slice("/", 1).to_int());
value_subtype_string = value_subtype_string.substr(0, value_slash_pos);
}
Variant::Type value_subtype = Variant::Type(value_subtype_string.to_int());
bool convert_value = value_subtype == Variant::OBJECT && value_subtype_hint == PROPERTY_HINT_NODE_TYPE;
if (convert_key || convert_value) {
use_deferred_node_path_bit = true;
Dictionary dict = value;
Dictionary new_dict;
for (int i = 0; i < dict.size(); i++) {
Variant new_key = dict.get_key_at_index(i);
if (convert_key && new_key.get_type() == Variant::OBJECT) {
if (Node *n = Object::cast_to<Node>(new_key)) {
new_key = p_node->get_path_to(n);
}
}
Variant new_value = dict.get_value_at_index(i);
if (convert_value && new_value.get_type() == Variant::OBJECT) {
if (Node *n = Object::cast_to<Node>(new_value)) {
new_value = p_node->get_path_to(n);
}
}
new_dict[new_key] = new_value;
}
value = new_dict;
}
}
}
if (!pinned_props.has(name)) {

View File

@ -158,6 +158,7 @@ public:
Node *instantiate(GenEditState p_edit_state) const;
Array setup_resources_in_array(Array &array_to_scan, const SceneState::NodeData &n, HashMap<Ref<Resource>, Ref<Resource>> &resources_local_to_sub_scene, Node *node, const StringName sname, HashMap<Ref<Resource>, Ref<Resource>> &resources_local_to_scene, int i, Node **ret_nodes, SceneState::GenEditState p_edit_state) const;
Dictionary setup_resources_in_dictionary(Dictionary &p_dictionary_to_scan, const SceneState::NodeData &p_n, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const;
Variant make_local_resource(Variant &value, const SceneState::NodeData &p_node_data, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const;
bool has_local_resource(const Array &p_array) const;

View File

@ -624,6 +624,19 @@ Error ResourceLoaderText::load() {
}
}
if (value.get_type() == Variant::DICTIONARY) {
Dictionary set_dict = value;
bool is_get_valid = false;
Variant get_value = res->get(assign, &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(assign, value);
}
@ -751,6 +764,19 @@ Error ResourceLoaderText::load() {
}
}
if (value.get_type() == Variant::DICTIONARY) {
Dictionary set_dict = value;
bool is_get_valid = false;
Variant get_value = resource->get(assign, &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) {
resource->set(assign, value);
}
@ -1642,6 +1668,8 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant,
} break;
case Variant::DICTIONARY: {
Dictionary d = p_variant;
_find_resources(d.get_typed_key_script());
_find_resources(d.get_typed_value_script());
List<Variant> keys;
d.get_key_list(&keys);
for (const Variant &E : keys) {

View File

@ -31,7 +31,7 @@
#ifndef TEST_DICTIONARY_H
#define TEST_DICTIONARY_H
#include "core/variant/dictionary.h"
#include "core/variant/typed_dictionary.h"
#include "tests/test_macros.h"
namespace TestDictionary {
@ -536,6 +536,43 @@ TEST_CASE("[Dictionary] Order and find") {
CHECK_EQ(d.find_key("does not exist"), Variant());
}
TEST_CASE("[Dictionary] Typed copying") {
TypedDictionary<int, int> d1;
d1[0] = 1;
TypedDictionary<double, double> d2;
d2[0] = 1.0;
Dictionary d3 = d1;
TypedDictionary<int, int> d4 = d3;
Dictionary d5 = d2;
TypedDictionary<int, int> d6 = d5;
d3[0] = 2;
d4[0] = 3;
// Same typed TypedDictionary should be shared.
CHECK_EQ(d1[0], Variant(3));
CHECK_EQ(d3[0], Variant(3));
CHECK_EQ(d4[0], Variant(3));
d5[0] = 2.0;
d6[0] = 3.0;
// Different typed TypedDictionary should not be shared.
CHECK_EQ(d2[0], Variant(2.0));
CHECK_EQ(d5[0], Variant(2.0));
CHECK_EQ(d6[0], Variant(3.0));
d1.clear();
d2.clear();
d3.clear();
d4.clear();
d5.clear();
d6.clear();
}
} // namespace TestDictionary
#endif // TEST_DICTIONARY_H