mirror of
https://github.com/godotengine/godot.git
synced 2024-11-21 11:32:13 +00:00
Implement typed dictionaries
This commit is contained in:
parent
906a4e9db9
commit
9853a69144
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
||||
/**
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
342
core/variant/typed_dictionary.h
Normal file
342
core/variant/typed_dictionary.h
Normal 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
|
@ -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());
|
||||
|
@ -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"));
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
|
@ -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 ///////////////////////////
|
||||
|
@ -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();
|
||||
|
@ -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 += ")";
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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>());
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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>());
|
||||
|
@ -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);
|
||||
|
@ -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";
|
||||
|
@ -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,
|
||||
|
@ -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.");
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -0,0 +1,3 @@
|
||||
func test():
|
||||
for key: int in { "a": 1 }:
|
||||
print(key)
|
@ -0,0 +1,2 @@
|
||||
GDTEST_ANALYZER_ERROR
|
||||
Cannot include a value of type "String" as "int".
|
@ -0,0 +1,4 @@
|
||||
func test():
|
||||
var differently: Dictionary[float, float] = { 1.0: 0.0 }
|
||||
var typed: Dictionary[int, int] = differently
|
||||
print('not ok')
|
@ -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].
|
@ -0,0 +1,2 @@
|
||||
func test():
|
||||
const dict: Dictionary[int, int] = { "Hello": "World" }
|
@ -0,0 +1,2 @@
|
||||
GDTEST_ANALYZER_ERROR
|
||||
Cannot include a value of type "String" as "int".
|
@ -0,0 +1,4 @@
|
||||
func test():
|
||||
var unconvertible := 1
|
||||
var typed: Dictionary[Object, Object] = { unconvertible: unconvertible }
|
||||
print('not ok')
|
@ -0,0 +1,2 @@
|
||||
GDTEST_ANALYZER_ERROR
|
||||
Cannot have a key of type "int" in a dictionary of type "Dictionary[Object, Object]".
|
@ -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')
|
@ -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]".
|
@ -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')
|
@ -0,0 +1,11 @@
|
||||
GDTEST_OK
|
||||
{ 0: 1 }
|
||||
0
|
||||
0
|
||||
{ 2: 3 }
|
||||
0
|
||||
0
|
||||
{ 4: 5 }
|
||||
2
|
||||
2
|
||||
ok
|
@ -0,0 +1,4 @@
|
||||
func test():
|
||||
var dict := { 0: 0 }
|
||||
dict[0] = 1
|
||||
print(dict[0])
|
@ -0,0 +1,2 @@
|
||||
GDTEST_OK
|
||||
1
|
@ -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')
|
@ -0,0 +1,2 @@
|
||||
GDTEST_OK
|
||||
ok
|
@ -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)
|
@ -0,0 +1,2 @@
|
||||
GDTEST_OK
|
||||
Inner
|
@ -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)
|
@ -0,0 +1,3 @@
|
||||
GDTEST_OK
|
||||
{ 1: "one", 2: "two", 3: "three" }
|
||||
{ 1: "one", 2: "two", 3: "three" }
|
@ -0,0 +1,4 @@
|
||||
func test():
|
||||
var basic := { 1: 1 }
|
||||
var typed: Dictionary[int, int] = basic
|
||||
print('not ok')
|
@ -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]".
|
@ -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')
|
@ -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]".
|
@ -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')
|
@ -0,0 +1,5 @@
|
||||
GDTEST_RUNTIME_ERROR
|
||||
>> ERROR
|
||||
>> Method/function failed.
|
||||
>> Unable to convert key from "Object" to "Object".
|
||||
not ok
|
@ -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')
|
@ -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.
|
@ -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')
|
@ -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.
|
@ -38,3 +38,8 @@ func test():
|
||||
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))
|
||||
|
@ -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"
|
||||
|
@ -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()
|
||||
|
@ -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])
|
||||
|
@ -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" })
|
@ -0,0 +1,3 @@
|
||||
GDTEST_OK
|
||||
true
|
||||
true
|
@ -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')
|
@ -0,0 +1,2 @@
|
||||
GDTEST_OK
|
||||
ok
|
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -88,7 +88,8 @@ namespace Godot.SourceGenerators
|
||||
HideQuaternionEdit = 35,
|
||||
Password = 36,
|
||||
LayersAvoidance = 37,
|
||||
Max = 38
|
||||
DictionaryType = 38,
|
||||
Max = 39
|
||||
}
|
||||
|
||||
[Flags]
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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)) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user