From e79be6ce07ed8c89011f759ecade070a3bd5a806 Mon Sep 17 00:00:00 2001 From: rune-scape Date: Mon, 5 Dec 2022 21:46:47 -0500 Subject: [PATCH] Unify String and StringName --- core/variant/array.cpp | 96 +- core/variant/container_type_validate.h | 23 +- core/variant/dictionary.cpp | 56 +- core/variant/variant.cpp | 13 + core/variant/variant.h | 4 + core/variant/variant_call.cpp | 341 +++++--- core/variant/variant_op.cpp | 117 +-- core/variant/variant_op.h | 73 +- doc/classes/String.xml | 6 + doc/classes/StringName.xml | 821 ++++++++++++++++++ editor/doc_tools.cpp | 4 +- modules/gdscript/gdscript_analyzer.cpp | 10 +- modules/gdscript/gdscript_compiler.cpp | 21 + ...dictionary_string_stringname_equivalent.gd | 9 + ...ictionary_string_stringname_equivalent.out | 2 + .../array_string_stringname_equivalent.gd | 8 + .../array_string_stringname_equivalent.out | 3 + .../array_string_stringname_equivalent.gd | 35 + .../array_string_stringname_equivalent.out | 11 + ...dictionary_string_stringname_equivalent.gd | 17 + ...ictionary_string_stringname_equivalent.out | 6 + .../match_string_stringname_equivalent.gd | 14 + .../match_string_stringname_equivalent.out | 3 + .../features/string_stringname_equivalent.gd | 11 + .../features/string_stringname_equivalent.out | 8 + modules/multiplayer/scene_rpc_interface.cpp | 2 +- modules/regex/regex.cpp | 3 +- tests/core/variant/test_dictionary.h | 13 + 28 files changed, 1463 insertions(+), 267 deletions(-) create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.out create mode 100644 modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.gd create mode 100644 modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.out create mode 100644 modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd create mode 100644 modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.out create mode 100644 modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd create mode 100644 modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out create mode 100644 modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd create mode 100644 modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out create mode 100644 modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd create mode 100644 modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out diff --git a/core/variant/array.cpp b/core/variant/array.cpp index 6c4e8ba45016..53891a9823d8 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -38,6 +38,7 @@ #include "core/templates/search_array.h" #include "core/templates/vector.h" #include "core/variant/callable.h" +#include "core/variant/dictionary.h" #include "core/variant/variant.h" class ArrayPrivate { @@ -201,16 +202,21 @@ uint32_t Array::recursive_hash(int recursion_count) const { } bool Array::_assign(const Array &p_array) { + bool can_convert = p_array._p->typed.type == Variant::NIL; + can_convert |= _p->typed.type == Variant::STRING && p_array._p->typed.type == Variant::STRING_NAME; + can_convert |= _p->typed.type == Variant::STRING_NAME && p_array._p->typed.type == Variant::STRING; + if (_p->typed.type != Variant::OBJECT && _p->typed.type == p_array._p->typed.type) { //same type or untyped, just reference, should be fine _ref(p_array); } else if (_p->typed.type == Variant::NIL) { //from typed to untyped, must copy, but this is cheap anyway _p->array = p_array._p->array; - } else if (p_array._p->typed.type == Variant::NIL) { //from untyped to typed, must try to check if they are all valid + } else if (can_convert) { //from untyped to typed, must try to check if they are all valid if (_p->typed.type == Variant::OBJECT) { //for objects, it needs full validation, either can be converted or fail for (int i = 0; i < p_array._p->array.size(); i++) { - if (!_p->typed.validate(p_array._p->array[i], "assign")) { + const Variant &element = p_array._p->array[i]; + if (element.get_type() != Variant::OBJECT || !_p->typed.validate_object(element, "assign")) { return false; } } @@ -255,16 +261,20 @@ void Array::operator=(const Array &p_array) { void Array::push_back(const Variant &p_value) { ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state."); - ERR_FAIL_COND(!_p->typed.validate(p_value, "push_back")); - _p->array.push_back(p_value); + Variant value = p_value; + ERR_FAIL_COND(!_p->typed.validate(value, "push_back")); + _p->array.push_back(value); } void Array::append_array(const Array &p_array) { ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state."); - for (int i = 0; i < p_array.size(); ++i) { - ERR_FAIL_COND(!_p->typed.validate(p_array[i], "append_array")); + + Vector validated_array = p_array._p->array; + for (int i = 0; i < validated_array.size(); ++i) { + ERR_FAIL_COND(!_p->typed.validate(validated_array.write[i], "append_array")); } - _p->array.append_array(p_array._p->array); + + _p->array.append_array(validated_array); } Error Array::resize(int p_new_size) { @@ -274,20 +284,23 @@ Error Array::resize(int p_new_size) { Error Array::insert(int p_pos, const Variant &p_value) { ERR_FAIL_COND_V_MSG(_p->read_only, ERR_LOCKED, "Array is in read-only state."); - ERR_FAIL_COND_V(!_p->typed.validate(p_value, "insert"), ERR_INVALID_PARAMETER); - return _p->array.insert(p_pos, p_value); + Variant value = p_value; + ERR_FAIL_COND_V(!_p->typed.validate(value, "insert"), ERR_INVALID_PARAMETER); + return _p->array.insert(p_pos, value); } void Array::fill(const Variant &p_value) { ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state."); - ERR_FAIL_COND(!_p->typed.validate(p_value, "fill")); - _p->array.fill(p_value); + Variant value = p_value; + ERR_FAIL_COND(!_p->typed.validate(value, "fill")); + _p->array.fill(value); } void Array::erase(const Variant &p_value) { ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state."); - ERR_FAIL_COND(!_p->typed.validate(p_value, "erase")); - _p->array.erase(p_value); + Variant value = p_value; + ERR_FAIL_COND(!_p->typed.validate(value, "erase")); + _p->array.erase(value); } Variant Array::front() const { @@ -306,15 +319,34 @@ Variant Array::pick_random() const { } int Array::find(const Variant &p_value, int p_from) const { - ERR_FAIL_COND_V(!_p->typed.validate(p_value, "find"), -1); - return _p->array.find(p_value, p_from); + if (_p->array.size() == 0) { + return -1; + } + Variant value = p_value; + ERR_FAIL_COND_V(!_p->typed.validate(value, "find"), -1); + + int ret = -1; + + if (p_from < 0 || size() == 0) { + return ret; + } + + for (int i = p_from; i < size(); i++) { + if (StringLikeVariantComparator::compare(_p->array[i], value)) { + ret = i; + break; + } + } + + return ret; } int Array::rfind(const Variant &p_value, int p_from) const { if (_p->array.size() == 0) { return -1; } - ERR_FAIL_COND_V(!_p->typed.validate(p_value, "rfind"), -1); + Variant value = p_value; + ERR_FAIL_COND_V(!_p->typed.validate(value, "rfind"), -1); if (p_from < 0) { // Relative offset from the end @@ -326,7 +358,7 @@ int Array::rfind(const Variant &p_value, int p_from) const { } for (int i = p_from; i >= 0; i--) { - if (_p->array[i] == p_value) { + if (StringLikeVariantComparator::compare(_p->array[i], value)) { return i; } } @@ -335,14 +367,15 @@ int Array::rfind(const Variant &p_value, int p_from) const { } int Array::count(const Variant &p_value) const { - ERR_FAIL_COND_V(!_p->typed.validate(p_value, "count"), 0); + Variant value = p_value; + ERR_FAIL_COND_V(!_p->typed.validate(value, "count"), 0); if (_p->array.size() == 0) { return 0; } int amount = 0; for (int i = 0; i < _p->array.size(); i++) { - if (_p->array[i] == p_value) { + if (StringLikeVariantComparator::compare(_p->array[i], value)) { amount++; } } @@ -351,9 +384,10 @@ int Array::count(const Variant &p_value) const { } bool Array::has(const Variant &p_value) const { - ERR_FAIL_COND_V(!_p->typed.validate(p_value, "use 'has'"), false); + Variant value = p_value; + ERR_FAIL_COND_V(!_p->typed.validate(value, "use 'has'"), false); - return _p->array.find(p_value, 0) != -1; + return find(value) != -1; } void Array::remove_at(int p_pos) { @@ -363,9 +397,10 @@ void Array::remove_at(int p_pos) { void Array::set(int p_idx, const Variant &p_value) { ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state."); - ERR_FAIL_COND(!_p->typed.validate(p_value, "set")); + Variant value = p_value; + ERR_FAIL_COND(!_p->typed.validate(value, "set")); - operator[](p_idx) = p_value; + operator[](p_idx) = value; } const Variant &Array::get(int p_idx) const { @@ -588,15 +623,17 @@ void Array::shuffle() { } int Array::bsearch(const Variant &p_value, bool p_before) { - ERR_FAIL_COND_V(!_p->typed.validate(p_value, "binary search"), -1); + Variant value = p_value; + ERR_FAIL_COND_V(!_p->typed.validate(value, "binary search"), -1); SearchArray avs; - return avs.bisect(_p->array.ptrw(), _p->array.size(), p_value, p_before); + return avs.bisect(_p->array.ptrw(), _p->array.size(), value, p_before); } int Array::bsearch_custom(const Variant &p_value, const Callable &p_callable, bool p_before) { - ERR_FAIL_COND_V(!_p->typed.validate(p_value, "custom binary search"), -1); + Variant value = p_value; + ERR_FAIL_COND_V(!_p->typed.validate(value, "custom binary search"), -1); - return _p->array.bsearch_custom(p_value, p_before, p_callable); + return _p->array.bsearch_custom(value, p_before, p_callable); } void Array::reverse() { @@ -606,8 +643,9 @@ void Array::reverse() { void Array::push_front(const Variant &p_value) { ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state."); - ERR_FAIL_COND(!_p->typed.validate(p_value, "push_front")); - _p->array.insert(0, p_value); + Variant value = p_value; + ERR_FAIL_COND(!_p->typed.validate(value, "push_front")); + _p->array.insert(0, value); } Variant Array::pop_back() { diff --git a/core/variant/container_type_validate.h b/core/variant/container_type_validate.h index 427a337aab13..9976fce47f87 100644 --- a/core/variant/container_type_validate.h +++ b/core/variant/container_type_validate.h @@ -74,22 +74,37 @@ struct ContainerTypeValidate { return true; } - _FORCE_INLINE_ bool validate(const Variant &p_variant, const char *p_operation = "use") { + // Coerces String and StringName into each other when needed. + _FORCE_INLINE_ bool validate(Variant &inout_variant, const char *p_operation = "use") { if (type == Variant::NIL) { return true; } - if (type != p_variant.get_type()) { - if (p_variant.get_type() == Variant::NIL && type == Variant::OBJECT) { + if (type != inout_variant.get_type()) { + if (inout_variant.get_type() == Variant::NIL && type == Variant::OBJECT) { + return true; + } + if (type == Variant::STRING && inout_variant.get_type() == Variant::STRING_NAME) { + inout_variant = String(inout_variant); + return true; + } else if (type == Variant::STRING_NAME && inout_variant.get_type() == Variant::STRING) { + inout_variant = StringName(inout_variant); return true; } - ERR_FAIL_V_MSG(false, "Attempted to " + String(p_operation) + " a variable of type '" + Variant::get_type_name(p_variant.get_type()) + "' into a " + where + " of type '" + Variant::get_type_name(type) + "'."); + ERR_FAIL_V_MSG(false, "Attempted to " + String(p_operation) + " a variable of type '" + Variant::get_type_name(inout_variant.get_type()) + "' into a " + where + " of type '" + Variant::get_type_name(type) + "'."); } if (type != Variant::OBJECT) { return true; } + + return validate_object(inout_variant, p_operation); + } + + _FORCE_INLINE_ bool validate_object(const Variant &p_variant, const char *p_operation = "use") { + ERR_FAIL_COND_V(p_variant.get_type() != Variant::OBJECT, false); + #ifdef DEBUG_ENABLED ObjectID object_id = p_variant; if (object_id == ObjectID()) { diff --git a/core/variant/dictionary.cpp b/core/variant/dictionary.cpp index c1cb782a57a7..77d9fb0bc289 100644 --- a/core/variant/dictionary.cpp +++ b/core/variant/dictionary.cpp @@ -42,7 +42,7 @@ struct DictionaryPrivate { SafeRefCount refcount; Variant *read_only = nullptr; // If enabled, a pointer is used to a temporary value that is used to return read-only values. - HashMap variant_map; + HashMap variant_map; }; void Dictionary::get_key_list(List *p_keys) const { @@ -100,24 +100,12 @@ Variant &Dictionary::operator[](const Variant &p_key) { } const Variant &Dictionary::operator[](const Variant &p_key) const { - if (p_key.get_type() == Variant::STRING_NAME) { - const StringName *sn = VariantInternal::get_string_name(&p_key); - return _p->variant_map[sn->operator String()]; - } else { - return _p->variant_map[p_key]; - } + // Will not insert key, so no conversion is necessary. + return _p->variant_map[p_key]; } const Variant *Dictionary::getptr(const Variant &p_key) const { - HashMap::ConstIterator E; - - if (p_key.get_type() == Variant::STRING_NAME) { - const StringName *sn = VariantInternal::get_string_name(&p_key); - E = ((const HashMap *)&_p->variant_map)->find(sn->operator String()); - } else { - E = ((const HashMap *)&_p->variant_map)->find(p_key); - } - + HashMap::ConstIterator E(_p->variant_map.find(p_key)); if (!E) { return nullptr; } @@ -125,14 +113,7 @@ const Variant *Dictionary::getptr(const Variant &p_key) const { } Variant *Dictionary::getptr(const Variant &p_key) { - HashMap::Iterator E; - - if (p_key.get_type() == Variant::STRING_NAME) { - const StringName *sn = VariantInternal::get_string_name(&p_key); - E = ((HashMap *)&_p->variant_map)->find(sn->operator String()); - } else { - E = ((HashMap *)&_p->variant_map)->find(p_key); - } + HashMap::Iterator E(_p->variant_map.find(p_key)); if (!E) { return nullptr; } @@ -145,14 +126,7 @@ Variant *Dictionary::getptr(const Variant &p_key) { } Variant Dictionary::get_valid(const Variant &p_key) const { - HashMap::ConstIterator E; - - if (p_key.get_type() == Variant::STRING_NAME) { - const StringName *sn = VariantInternal::get_string_name(&p_key); - E = ((const HashMap *)&_p->variant_map)->find(sn->operator String()); - } else { - E = ((const HashMap *)&_p->variant_map)->find(p_key); - } + HashMap::ConstIterator E(_p->variant_map.find(p_key)); if (!E) { return Variant(); @@ -178,12 +152,7 @@ bool Dictionary::is_empty() const { } bool Dictionary::has(const Variant &p_key) const { - if (p_key.get_type() == Variant::STRING_NAME) { - const StringName *sn = VariantInternal::get_string_name(&p_key); - return _p->variant_map.has(sn->operator String()); - } else { - return _p->variant_map.has(p_key); - } + return _p->variant_map.has(p_key); } bool Dictionary::has_all(const Array &p_keys) const { @@ -206,12 +175,7 @@ Variant Dictionary::find_key(const Variant &p_value) const { bool Dictionary::erase(const Variant &p_key) { ERR_FAIL_COND_V_MSG(_p->read_only, false, "Dictionary is in read-only state."); - if (p_key.get_type() == Variant::STRING_NAME) { - const StringName *sn = VariantInternal::get_string_name(&p_key); - return _p->variant_map.erase(sn->operator String()); - } else { - return _p->variant_map.erase(p_key); - } + return _p->variant_map.erase(p_key); } bool Dictionary::operator==(const Dictionary &p_dictionary) const { @@ -238,7 +202,7 @@ bool Dictionary::recursive_equal(const Dictionary &p_dictionary, int recursion_c } recursion_count++; for (const KeyValue &this_E : _p->variant_map) { - HashMap::ConstIterator other_E = ((const HashMap *)&p_dictionary._p->variant_map)->find(this_E.key); + HashMap::ConstIterator other_E(p_dictionary._p->variant_map.find(this_E.key)); if (!other_E || !this_E.value.hash_compare(other_E->value, recursion_count)) { return false; } @@ -360,7 +324,7 @@ const Variant *Dictionary::next(const Variant *p_key) const { } return nullptr; } - HashMap::Iterator E = _p->variant_map.find(*p_key); + HashMap::Iterator E = _p->variant_map.find(*p_key); if (!E) { return nullptr; diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index c1166a0a14b1..894557516ef4 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -3491,6 +3491,19 @@ bool Variant::hash_compare(const Variant &p_variant, int recursion_count) const } } +bool StringLikeVariantComparator::compare(const Variant &p_lhs, const Variant &p_rhs) { + if (p_lhs.hash_compare(p_rhs)) { + return true; + } + if (p_lhs.get_type() == Variant::STRING && p_rhs.get_type() == Variant::STRING_NAME) { + return *VariantInternal::get_string(&p_lhs) == *VariantInternal::get_string_name(&p_rhs); + } + if (p_lhs.get_type() == Variant::STRING_NAME && p_rhs.get_type() == Variant::STRING) { + return *VariantInternal::get_string_name(&p_lhs) == *VariantInternal::get_string(&p_rhs); + } + return false; +} + bool Variant::is_ref_counted() const { return type == OBJECT && _get_obj().id.is_ref_counted(); } diff --git a/core/variant/variant.h b/core/variant/variant.h index c5be609184ed..f7221e206f54 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -797,6 +797,10 @@ struct VariantComparator { static _FORCE_INLINE_ bool compare(const Variant &p_lhs, const Variant &p_rhs) { return p_lhs.hash_compare(p_rhs); } }; +struct StringLikeVariantComparator { + static bool compare(const Variant &p_lhs, const Variant &p_rhs); +}; + Variant::ObjData &Variant::_get_obj() { return *reinterpret_cast(&_data._mem[0]); } diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index 2cb80dcab4ba..ac569941bf01 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -73,6 +73,30 @@ static _FORCE_INLINE_ void vc_method_call(void (T::*method)(P...) const, Variant call_with_variant_argsc_dv(VariantGetInternalPtr::get_ptr(base), method, p_args, p_argcount, r_error, p_defvals); } +template +static _FORCE_INLINE_ void vc_convert_method_call(R (T::*method)(P...), Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector &p_defvals, Callable::CallError &r_error) { + T converted(static_cast(*VariantGetInternalPtr::get_ptr(base))); + call_with_variant_args_ret_dv(&converted, method, p_args, p_argcount, r_ret, r_error, p_defvals); +} + +template +static _FORCE_INLINE_ void vc_convert_method_call(R (T::*method)(P...) const, Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector &p_defvals, Callable::CallError &r_error) { + T converted(static_cast(*VariantGetInternalPtr::get_ptr(base))); + call_with_variant_args_retc_dv(&converted, method, p_args, p_argcount, r_ret, r_error, p_defvals); +} + +template +static _FORCE_INLINE_ void vc_convert_method_call(void (T::*method)(P...), Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector &p_defvals, Callable::CallError &r_error) { + T converted(static_cast(*VariantGetInternalPtr::get_ptr(base))); + call_with_variant_args_dv(&converted, method, p_args, p_argcount, r_error, p_defvals); +} + +template +static _FORCE_INLINE_ void vc_convert_method_call(void (T::*method)(P...) const, Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector &p_defvals, Callable::CallError &r_error) { + T converted(static_cast(*VariantGetInternalPtr::get_ptr(base))); + call_with_variant_argsc_dv(&converted, method, p_args, p_argcount, r_error, p_defvals); +} + template static _FORCE_INLINE_ void vc_method_call_static(R (*method)(T *, P...), Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector &p_defvals, Callable::CallError &r_error) { call_with_variant_args_retc_static_helper_dv(VariantGetInternalPtr::get_ptr(base), method, p_args, p_argcount, r_ret, p_defvals, r_error); @@ -102,6 +126,29 @@ static _FORCE_INLINE_ void vc_validated_call(void (T::*method)(P...) const, Vari call_with_validated_variant_argsc(base, method, p_args); } +template +static _FORCE_INLINE_ void vc_convert_validated_call(R (T::*method)(P...), Variant *base, const Variant **p_args, Variant *r_ret) { + T converted(static_cast(*VariantGetInternalPtr::get_ptr(base))); + call_with_validated_variant_args_ret_helper(&converted, method, p_args, r_ret, BuildIndexSequence{}); +} + +template +static _FORCE_INLINE_ void vc_convert_validated_call(R (T::*method)(P...) const, Variant *base, const Variant **p_args, Variant *r_ret) { + T converted(static_cast(*VariantGetInternalPtr::get_ptr(base))); + call_with_validated_variant_args_retc_helper(&converted, method, p_args, r_ret, BuildIndexSequence{}); +} +template +static _FORCE_INLINE_ void vc_convert_validated_call(void (T::*method)(P...), Variant *base, const Variant **p_args, Variant *r_ret) { + T converted(static_cast(*VariantGetInternalPtr::get_ptr(base))); + call_with_validated_variant_args_helper(&converted, method, p_args, r_ret, BuildIndexSequence{}); +} + +template +static _FORCE_INLINE_ void vc_convert_validated_call(void (T::*method)(P...) const, Variant *base, const Variant **p_args, Variant *r_ret) { + T converted(static_cast(*VariantGetInternalPtr::get_ptr(base))); + call_with_validated_variant_argsc_helper(&converted, method, p_args, r_ret, BuildIndexSequence{}); +} + template static _FORCE_INLINE_ void vc_validated_call_static(R (*method)(T *, P...), Variant *base, const Variant **p_args, Variant *r_ret) { call_with_validated_variant_args_static_retc(base, method, p_args, r_ret); @@ -142,6 +189,30 @@ static _FORCE_INLINE_ void vc_ptrcall(void (T::*method)(P...) const, void *p_bas call_with_ptr_argsc(reinterpret_cast(p_base), method, p_args); } +template +static _FORCE_INLINE_ void vc_convert_ptrcall(R (T::*method)(P...), void *p_base, const void **p_args, void *r_ret) { + T converted(*reinterpret_cast(p_base)); + call_with_ptr_args_ret(&converted, method, p_args, r_ret); +} + +template +static _FORCE_INLINE_ void vc_convert_ptrcall(R (T::*method)(P...) const, void *p_base, const void **p_args, void *r_ret) { + T converted(*reinterpret_cast(p_base)); + call_with_ptr_args_retc(&converted, method, p_args, r_ret); +} + +template +static _FORCE_INLINE_ void vc_convert_ptrcall(void (T::*method)(P...), void *p_base, const void **p_args, void *r_ret) { + T converted(*reinterpret_cast(p_base)); + call_with_ptr_args(&converted, method, p_args); +} + +template +static _FORCE_INLINE_ void vc_convert_ptrcall(void (T::*method)(P...) const, void *p_base, const void **p_args, void *r_ret) { + T converted(*reinterpret_cast(p_base)); + call_with_ptr_argsc(&converted, method, p_args); +} + template static _FORCE_INLINE_ int vc_get_argument_count(R (T::*method)(P...)) { return sizeof...(P); @@ -337,6 +408,46 @@ static _FORCE_INLINE_ Variant::Type vc_get_base_type(void (T::*method)(P...) con } \ }; +#define CONVERT_METHOD_CLASS(m_class, m_method_name, m_method_ptr) \ + struct Method_##m_class##_##m_method_name { \ + static void call(Variant *base, const Variant **p_args, int p_argcount, Variant &r_ret, const Vector &p_defvals, Callable::CallError &r_error) { \ + vc_convert_method_call(m_method_ptr, base, p_args, p_argcount, r_ret, p_defvals, r_error); \ + } \ + static void validated_call(Variant *base, const Variant **p_args, int p_argcount, Variant *r_ret) { \ + vc_convert_validated_call(m_method_ptr, base, p_args, r_ret); \ + } \ + static void ptrcall(void *p_base, const void **p_args, void *r_ret, int p_argcount) { \ + vc_convert_ptrcall(m_method_ptr, p_base, p_args, r_ret); \ + } \ + static int get_argument_count() { \ + return vc_get_argument_count(m_method_ptr); \ + } \ + static Variant::Type get_argument_type(int p_arg) { \ + return vc_get_argument_type(m_method_ptr, p_arg); \ + } \ + static Variant::Type get_return_type() { \ + return vc_get_return_type(m_method_ptr); \ + } \ + static bool has_return_type() { \ + return vc_has_return_type(m_method_ptr); \ + } \ + static bool is_const() { \ + return vc_is_const(m_method_ptr); \ + } \ + static bool is_static() { \ + return false; \ + } \ + static bool is_vararg() { \ + return false; \ + } \ + static Variant::Type get_base_type() { \ + return GetTypeInfo::VARIANT_TYPE; \ + } \ + static StringName get_name() { \ + return #m_method_name; \ + } \ + }; + template static _FORCE_INLINE_ void vc_static_ptrcall(R (*method)(P...), const void **p_args, void *r_ret) { call_with_ptr_args_static_method_ret(method, p_args, r_ret); @@ -1421,6 +1532,16 @@ int Variant::get_enum_value(Variant::Type p_type, StringName p_enum_name, String register_builtin_method(sarray(), m_default_args); #endif +#ifdef DEBUG_METHODS_ENABLED +#define bind_convert_method(m_type_from, m_type_to, m_method, m_arg_names, m_default_args) \ + CONVERT_METHOD_CLASS(m_type_from, m_method, &m_type_to::m_method); \ + register_builtin_method(m_arg_names, m_default_args); +#else +#define bind_convert_method(m_type_from, m_type_to, m_method, m_arg_names, m_default_args) \ + CONVERT_METHOD_CLASS(m_type_from, m_method, &m_type_to ::m_method); \ + register_builtin_method(sarray(), m_default_args); +#endif + #ifdef DEBUG_METHODS_ENABLED #define bind_static_method(m_type, m_method, m_arg_names, m_default_args) \ STATIC_METHOD_CLASS(m_type, m_method, m_type::m_method); \ @@ -1441,6 +1562,16 @@ int Variant::get_enum_value(Variant::Type p_type, StringName p_enum_name, String register_builtin_method(sarray(), m_default_args); #endif +#ifdef DEBUG_METHODS_ENABLED +#define bind_convert_methodv(m_type_from, m_type_to, m_name, m_method, m_arg_names, m_default_args) \ + CONVERT_METHOD_CLASS(m_type_from, m_name, m_method); \ + register_builtin_method(m_arg_names, m_default_args); +#else +#define bind_convert_methodv(m_type_from, m_type_to, m_name, m_method, m_arg_names, m_default_args) \ + CONVERT_METHOD_CLASS(m_type_from, m_name, m_method); \ + register_builtin_method(sarray(), m_default_args); +#endif + #ifdef DEBUG_METHODS_ENABLED #define bind_function(m_type, m_name, m_method, m_arg_names, m_default_args) \ FUNCTION_CLASS(m_type, m_name, m_method, true); \ @@ -1461,6 +1592,14 @@ int Variant::get_enum_value(Variant::Type p_type, StringName p_enum_name, String register_builtin_method(sarray(), m_default_args); #endif +#define bind_string_method(m_method, m_arg_names, m_default_args) \ + bind_method(String, m_method, m_arg_names, m_default_args); \ + bind_convert_method(StringName, String, m_method, m_arg_names, m_default_args); + +#define bind_string_methodv(m_name, m_method, m_arg_names, m_default_args) \ + bind_methodv(String, m_name, m_method, m_arg_names, m_default_args); \ + bind_convert_methodv(StringName, String, m_name, m_method, m_arg_names, m_default_args); + #define bind_custom(m_type, m_name, m_method, m_has_return, m_ret_type) \ VARARG_CLASS(m_type, m_name, m_method, m_has_return, m_ret_type) \ register_builtin_method(sarray(), Vector()); @@ -1477,108 +1616,108 @@ static void _register_variant_builtin_methods() { /* String */ - bind_method(String, casecmp_to, sarray("to"), varray()); - bind_method(String, nocasecmp_to, sarray("to"), varray()); - bind_method(String, naturalnocasecmp_to, sarray("to"), varray()); - bind_method(String, length, sarray(), varray()); - bind_method(String, substr, sarray("from", "len"), varray(-1)); - bind_method(String, get_slice, sarray("delimiter", "slice"), varray()); - bind_method(String, get_slicec, sarray("delimiter", "slice"), varray()); - bind_method(String, get_slice_count, sarray("delimiter"), varray()); - bind_methodv(String, find, static_cast(&String::find), sarray("what", "from"), varray(0)); - bind_method(String, count, sarray("what", "from", "to"), varray(0, 0)); - bind_method(String, countn, sarray("what", "from", "to"), varray(0, 0)); - bind_method(String, findn, sarray("what", "from"), varray(0)); - bind_method(String, rfind, sarray("what", "from"), varray(-1)); - bind_method(String, rfindn, sarray("what", "from"), varray(-1)); - bind_method(String, match, sarray("expr"), varray()); - bind_method(String, matchn, sarray("expr"), varray()); - bind_methodv(String, begins_with, static_cast(&String::begins_with), sarray("text"), varray()); - bind_method(String, ends_with, sarray("text"), varray()); - bind_method(String, is_subsequence_of, sarray("text"), varray()); - bind_method(String, is_subsequence_ofn, sarray("text"), varray()); - bind_method(String, bigrams, sarray(), varray()); - bind_method(String, similarity, sarray("text"), varray()); - - bind_method(String, format, sarray("values", "placeholder"), varray("{_}")); - bind_methodv(String, replace, static_cast(&String::replace), sarray("what", "forwhat"), varray()); - bind_method(String, replacen, sarray("what", "forwhat"), varray()); - bind_method(String, repeat, sarray("count"), varray()); - bind_method(String, insert, sarray("position", "what"), varray()); - bind_method(String, capitalize, sarray(), varray()); - bind_method(String, to_camel_case, sarray(), varray()); - bind_method(String, to_pascal_case, sarray(), varray()); - bind_method(String, to_snake_case, sarray(), varray()); - bind_method(String, split, sarray("delimiter", "allow_empty", "maxsplit"), varray("", true, 0)); - bind_method(String, rsplit, sarray("delimiter", "allow_empty", "maxsplit"), varray("", true, 0)); - bind_method(String, split_floats, sarray("delimiter", "allow_empty"), varray(true)); - bind_method(String, join, sarray("parts"), varray()); - - bind_method(String, to_upper, sarray(), varray()); - bind_method(String, to_lower, sarray(), varray()); - - bind_method(String, left, sarray("length"), varray()); - bind_method(String, right, sarray("length"), varray()); - - bind_method(String, strip_edges, sarray("left", "right"), varray(true, true)); - bind_method(String, strip_escapes, sarray(), varray()); - bind_method(String, lstrip, sarray("chars"), varray()); - bind_method(String, rstrip, sarray("chars"), varray()); - bind_method(String, get_extension, sarray(), varray()); - bind_method(String, get_basename, sarray(), varray()); - bind_method(String, path_join, sarray("file"), varray()); - bind_method(String, unicode_at, sarray("at"), varray()); - bind_method(String, indent, sarray("prefix"), varray()); - bind_method(String, dedent, sarray(), varray()); + bind_string_method(casecmp_to, sarray("to"), varray()); + bind_string_method(nocasecmp_to, sarray("to"), varray()); + bind_string_method(naturalnocasecmp_to, sarray("to"), varray()); + bind_string_method(length, sarray(), varray()); + bind_string_method(substr, sarray("from", "len"), varray(-1)); + bind_string_method(get_slice, sarray("delimiter", "slice"), varray()); + bind_string_method(get_slicec, sarray("delimiter", "slice"), varray()); + bind_string_method(get_slice_count, sarray("delimiter"), varray()); + bind_string_methodv(find, static_cast(&String::find), sarray("what", "from"), varray(0)); + bind_string_method(count, sarray("what", "from", "to"), varray(0, 0)); + bind_string_method(countn, sarray("what", "from", "to"), varray(0, 0)); + bind_string_method(findn, sarray("what", "from"), varray(0)); + bind_string_method(rfind, sarray("what", "from"), varray(-1)); + bind_string_method(rfindn, sarray("what", "from"), varray(-1)); + bind_string_method(match, sarray("expr"), varray()); + bind_string_method(matchn, sarray("expr"), varray()); + bind_string_methodv(begins_with, static_cast(&String::begins_with), sarray("text"), varray()); + bind_string_method(ends_with, sarray("text"), varray()); + bind_string_method(is_subsequence_of, sarray("text"), varray()); + bind_string_method(is_subsequence_ofn, sarray("text"), varray()); + bind_string_method(bigrams, sarray(), varray()); + bind_string_method(similarity, sarray("text"), varray()); + + bind_string_method(format, sarray("values", "placeholder"), varray("{_}")); + bind_string_methodv(replace, static_cast(&String::replace), sarray("what", "forwhat"), varray()); + bind_string_method(replacen, sarray("what", "forwhat"), varray()); + bind_string_method(repeat, sarray("count"), varray()); + bind_string_method(insert, sarray("position", "what"), varray()); + bind_string_method(capitalize, sarray(), varray()); + bind_string_method(to_camel_case, sarray(), varray()); + bind_string_method(to_pascal_case, sarray(), varray()); + bind_string_method(to_snake_case, sarray(), varray()); + bind_string_method(split, sarray("delimiter", "allow_empty", "maxsplit"), varray("", true, 0)); + bind_string_method(rsplit, sarray("delimiter", "allow_empty", "maxsplit"), varray("", true, 0)); + bind_string_method(split_floats, sarray("delimiter", "allow_empty"), varray(true)); + bind_string_method(join, sarray("parts"), varray()); + + bind_string_method(to_upper, sarray(), varray()); + bind_string_method(to_lower, sarray(), varray()); + + bind_string_method(left, sarray("length"), varray()); + bind_string_method(right, sarray("length"), varray()); + + bind_string_method(strip_edges, sarray("left", "right"), varray(true, true)); + bind_string_method(strip_escapes, sarray(), varray()); + bind_string_method(lstrip, sarray("chars"), varray()); + bind_string_method(rstrip, sarray("chars"), varray()); + bind_string_method(get_extension, sarray(), varray()); + bind_string_method(get_basename, sarray(), varray()); + bind_string_method(path_join, sarray("file"), varray()); + bind_string_method(unicode_at, sarray("at"), varray()); + bind_string_method(indent, sarray("prefix"), varray()); + bind_string_method(dedent, sarray(), varray()); bind_method(String, hash, sarray(), varray()); - bind_method(String, md5_text, sarray(), varray()); - bind_method(String, sha1_text, sarray(), varray()); - bind_method(String, sha256_text, sarray(), varray()); - bind_method(String, md5_buffer, sarray(), varray()); - bind_method(String, sha1_buffer, sarray(), varray()); - bind_method(String, sha256_buffer, sarray(), varray()); - bind_method(String, is_empty, sarray(), varray()); - bind_methodv(String, contains, static_cast(&String::contains), sarray("what"), varray()); - - bind_method(String, is_absolute_path, sarray(), varray()); - bind_method(String, is_relative_path, sarray(), varray()); - bind_method(String, simplify_path, sarray(), varray()); - bind_method(String, get_base_dir, sarray(), varray()); - bind_method(String, get_file, sarray(), varray()); - bind_method(String, xml_escape, sarray("escape_quotes"), varray(false)); - bind_method(String, xml_unescape, sarray(), varray()); - bind_method(String, uri_encode, sarray(), varray()); - bind_method(String, uri_decode, sarray(), varray()); - bind_method(String, c_escape, sarray(), varray()); - bind_method(String, c_unescape, sarray(), varray()); - bind_method(String, json_escape, sarray(), varray()); - - bind_method(String, validate_node_name, sarray(), varray()); - - bind_method(String, is_valid_identifier, sarray(), varray()); - bind_method(String, is_valid_int, sarray(), varray()); - bind_method(String, is_valid_float, sarray(), varray()); - bind_method(String, is_valid_hex_number, sarray("with_prefix"), varray(false)); - bind_method(String, is_valid_html_color, sarray(), varray()); - bind_method(String, is_valid_ip_address, sarray(), varray()); - bind_method(String, is_valid_filename, sarray(), varray()); - - bind_method(String, to_int, sarray(), varray()); - bind_method(String, to_float, sarray(), varray()); - bind_method(String, hex_to_int, sarray(), varray()); - bind_method(String, bin_to_int, sarray(), varray()); - - bind_method(String, lpad, sarray("min_length", "character"), varray(" ")); - bind_method(String, rpad, sarray("min_length", "character"), varray(" ")); - bind_method(String, pad_decimals, sarray("digits"), varray()); - bind_method(String, pad_zeros, sarray("digits"), varray()); - bind_method(String, trim_prefix, sarray("prefix"), varray()); - bind_method(String, trim_suffix, sarray("suffix"), varray()); - - bind_method(String, to_ascii_buffer, sarray(), varray()); - bind_method(String, to_utf8_buffer, sarray(), varray()); - bind_method(String, to_utf16_buffer, sarray(), varray()); - bind_method(String, to_utf32_buffer, sarray(), varray()); + bind_string_method(md5_text, sarray(), varray()); + bind_string_method(sha1_text, sarray(), varray()); + bind_string_method(sha256_text, sarray(), varray()); + bind_string_method(md5_buffer, sarray(), varray()); + bind_string_method(sha1_buffer, sarray(), varray()); + bind_string_method(sha256_buffer, sarray(), varray()); + bind_string_method(is_empty, sarray(), varray()); + bind_string_methodv(contains, static_cast(&String::contains), sarray("what"), varray()); + + bind_string_method(is_absolute_path, sarray(), varray()); + bind_string_method(is_relative_path, sarray(), varray()); + bind_string_method(simplify_path, sarray(), varray()); + bind_string_method(get_base_dir, sarray(), varray()); + bind_string_method(get_file, sarray(), varray()); + bind_string_method(xml_escape, sarray("escape_quotes"), varray(false)); + bind_string_method(xml_unescape, sarray(), varray()); + bind_string_method(uri_encode, sarray(), varray()); + bind_string_method(uri_decode, sarray(), varray()); + bind_string_method(c_escape, sarray(), varray()); + bind_string_method(c_unescape, sarray(), varray()); + bind_string_method(json_escape, sarray(), varray()); + + bind_string_method(validate_node_name, sarray(), varray()); + + bind_string_method(is_valid_identifier, sarray(), varray()); + bind_string_method(is_valid_int, sarray(), varray()); + bind_string_method(is_valid_float, sarray(), varray()); + bind_string_method(is_valid_hex_number, sarray("with_prefix"), varray(false)); + bind_string_method(is_valid_html_color, sarray(), varray()); + bind_string_method(is_valid_ip_address, sarray(), varray()); + bind_string_method(is_valid_filename, sarray(), varray()); + + bind_string_method(to_int, sarray(), varray()); + bind_string_method(to_float, sarray(), varray()); + bind_string_method(hex_to_int, sarray(), varray()); + bind_string_method(bin_to_int, sarray(), varray()); + + bind_string_method(lpad, sarray("min_length", "character"), varray(" ")); + bind_string_method(rpad, sarray("min_length", "character"), varray(" ")); + bind_string_method(pad_decimals, sarray("digits"), varray()); + bind_string_method(pad_zeros, sarray("digits"), varray()); + bind_string_method(trim_prefix, sarray("prefix"), varray()); + bind_string_method(trim_suffix, sarray("suffix"), varray()); + + bind_string_method(to_ascii_buffer, sarray(), varray()); + bind_string_method(to_utf8_buffer, sarray(), varray()); + bind_string_method(to_utf16_buffer, sarray(), varray()); + bind_string_method(to_utf32_buffer, sarray(), varray()); bind_static_method(String, num_scientific, sarray("number"), varray()); bind_static_method(String, num, sarray("number", "decimals"), varray(-1)); diff --git a/core/variant/variant_op.cpp b/core/variant/variant_op.cpp index 25bc241e9b2c..d4e6dfb4d18a 100644 --- a/core/variant/variant_op.cpp +++ b/core/variant/variant_op.cpp @@ -229,6 +229,20 @@ class OperatorEvaluatorDivNZ { static Variant::Type get_return_type() { return GetTypeInfo::VARIANT_TYPE; } }; +#define register_string_op(m_op_type, m_op_code) \ + do { \ + register_op>(m_op_code, Variant::STRING, Variant::STRING); \ + register_op>(m_op_code, Variant::STRING, Variant::STRING_NAME); \ + register_op>(m_op_code, Variant::STRING_NAME, Variant::STRING); \ + register_op>(m_op_code, Variant::STRING_NAME, Variant::STRING_NAME); \ + } while (false) + +#define register_string_modulo_op(m_class, m_type) \ + do { \ + register_op>(Variant::OP_MODULE, Variant::STRING, m_type); \ + register_op>(Variant::OP_MODULE, Variant::STRING_NAME, m_type); \ + } while (false) + void Variant::_register_variant_operators() { memset(operator_return_type_table, 0, sizeof(operator_return_type_table)); memset(operator_evaluator_table, 0, sizeof(operator_evaluator_table)); @@ -239,7 +253,7 @@ void Variant::_register_variant_operators() { register_op>(Variant::OP_ADD, Variant::INT, Variant::FLOAT); register_op>(Variant::OP_ADD, Variant::FLOAT, Variant::INT); register_op>(Variant::OP_ADD, Variant::FLOAT, Variant::FLOAT); - register_op>(Variant::OP_ADD, Variant::STRING, Variant::STRING); + register_string_op(OperatorEvaluatorStringConcat, Variant::OP_ADD); register_op>(Variant::OP_ADD, Variant::VECTOR2, Variant::VECTOR2); register_op>(Variant::OP_ADD, Variant::VECTOR2I, Variant::VECTOR2I); register_op>(Variant::OP_ADD, Variant::VECTOR3, Variant::VECTOR3); @@ -415,46 +429,46 @@ void Variant::_register_variant_operators() { register_op>(Variant::OP_MODULE, Variant::VECTOR4I, Variant::VECTOR4I); register_op>(Variant::OP_MODULE, Variant::VECTOR4I, Variant::INT); - register_op(Variant::OP_MODULE, Variant::STRING, Variant::NIL); - - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::BOOL); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::INT); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::FLOAT); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::STRING); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::VECTOR2); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::VECTOR2I); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::RECT2); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::RECT2I); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::VECTOR3); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::VECTOR3I); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::VECTOR4); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::VECTOR4I); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::TRANSFORM2D); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::PLANE); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::QUATERNION); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::AABB); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::BASIS); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::TRANSFORM3D); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::PROJECTION); - - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::COLOR); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::STRING_NAME); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::NODE_PATH); - register_op(Variant::OP_MODULE, Variant::STRING, Variant::OBJECT); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::CALLABLE); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::SIGNAL); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::DICTIONARY); - register_op(Variant::OP_MODULE, Variant::STRING, Variant::ARRAY); - - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::PACKED_BYTE_ARRAY); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::PACKED_INT32_ARRAY); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::PACKED_INT64_ARRAY); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::PACKED_FLOAT32_ARRAY); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::PACKED_FLOAT64_ARRAY); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::PACKED_STRING_ARRAY); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::PACKED_VECTOR2_ARRAY); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::PACKED_VECTOR3_ARRAY); - register_op>(Variant::OP_MODULE, Variant::STRING, Variant::PACKED_COLOR_ARRAY); + register_string_modulo_op(void, Variant::NIL); + + register_string_modulo_op(bool, Variant::BOOL); + register_string_modulo_op(int64_t, Variant::INT); + register_string_modulo_op(double, Variant::FLOAT); + register_string_modulo_op(String, Variant::STRING); + register_string_modulo_op(Vector2, Variant::VECTOR2); + register_string_modulo_op(Vector2i, Variant::VECTOR2I); + register_string_modulo_op(Rect2, Variant::RECT2); + register_string_modulo_op(Rect2i, Variant::RECT2I); + register_string_modulo_op(Vector3, Variant::VECTOR3); + register_string_modulo_op(Vector3i, Variant::VECTOR3I); + register_string_modulo_op(Vector4, Variant::VECTOR4); + register_string_modulo_op(Vector4i, Variant::VECTOR4I); + register_string_modulo_op(Transform2D, Variant::TRANSFORM2D); + register_string_modulo_op(Plane, Variant::PLANE); + register_string_modulo_op(Quaternion, Variant::QUATERNION); + register_string_modulo_op(::AABB, Variant::AABB); + register_string_modulo_op(Basis, Variant::BASIS); + register_string_modulo_op(Transform3D, Variant::TRANSFORM3D); + register_string_modulo_op(Projection, Variant::PROJECTION); + + register_string_modulo_op(Color, Variant::COLOR); + register_string_modulo_op(StringName, Variant::STRING_NAME); + register_string_modulo_op(NodePath, Variant::NODE_PATH); + register_string_modulo_op(Object, Variant::OBJECT); + register_string_modulo_op(Callable, Variant::CALLABLE); + register_string_modulo_op(Signal, Variant::SIGNAL); + register_string_modulo_op(Dictionary, Variant::DICTIONARY); + register_string_modulo_op(Array, Variant::ARRAY); + + register_string_modulo_op(PackedByteArray, Variant::PACKED_BYTE_ARRAY); + register_string_modulo_op(PackedInt32Array, Variant::PACKED_INT32_ARRAY); + register_string_modulo_op(PackedInt64Array, Variant::PACKED_INT64_ARRAY); + register_string_modulo_op(PackedFloat32Array, Variant::PACKED_FLOAT32_ARRAY); + register_string_modulo_op(PackedFloat64Array, Variant::PACKED_FLOAT64_ARRAY); + register_string_modulo_op(PackedStringArray, Variant::PACKED_STRING_ARRAY); + register_string_modulo_op(PackedVector2Array, Variant::PACKED_VECTOR2_ARRAY); + register_string_modulo_op(PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY); + register_string_modulo_op(PackedColorArray, Variant::PACKED_COLOR_ARRAY); register_op>(Variant::OP_POWER, Variant::INT, Variant::INT); register_op>(Variant::OP_POWER, Variant::INT, Variant::FLOAT); @@ -498,7 +512,7 @@ void Variant::_register_variant_operators() { register_op>(Variant::OP_EQUAL, Variant::INT, Variant::FLOAT); register_op>(Variant::OP_EQUAL, Variant::FLOAT, Variant::INT); register_op>(Variant::OP_EQUAL, Variant::FLOAT, Variant::FLOAT); - register_op>(Variant::OP_EQUAL, Variant::STRING, Variant::STRING); + register_string_op(OperatorEvaluatorEqual, Variant::OP_EQUAL); register_op>(Variant::OP_EQUAL, Variant::VECTOR2, Variant::VECTOR2); register_op>(Variant::OP_EQUAL, Variant::VECTOR2I, Variant::VECTOR2I); register_op>(Variant::OP_EQUAL, Variant::RECT2, Variant::RECT2); @@ -516,10 +530,6 @@ void Variant::_register_variant_operators() { register_op>(Variant::OP_EQUAL, Variant::PROJECTION, Variant::PROJECTION); register_op>(Variant::OP_EQUAL, Variant::COLOR, Variant::COLOR); - register_op>(Variant::OP_EQUAL, Variant::STRING_NAME, Variant::STRING); - register_op>(Variant::OP_EQUAL, Variant::STRING, Variant::STRING_NAME); - register_op>(Variant::OP_EQUAL, Variant::STRING_NAME, Variant::STRING_NAME); - register_op>(Variant::OP_EQUAL, Variant::NODE_PATH, Variant::NODE_PATH); register_op>(Variant::OP_EQUAL, Variant::RID, Variant::RID); @@ -621,7 +631,7 @@ void Variant::_register_variant_operators() { register_op>(Variant::OP_NOT_EQUAL, Variant::INT, Variant::FLOAT); register_op>(Variant::OP_NOT_EQUAL, Variant::FLOAT, Variant::INT); register_op>(Variant::OP_NOT_EQUAL, Variant::FLOAT, Variant::FLOAT); - register_op>(Variant::OP_NOT_EQUAL, Variant::STRING, Variant::STRING); + register_string_op(OperatorEvaluatorNotEqual, Variant::OP_NOT_EQUAL); register_op>(Variant::OP_NOT_EQUAL, Variant::VECTOR2, Variant::VECTOR2); register_op>(Variant::OP_NOT_EQUAL, Variant::VECTOR2I, Variant::VECTOR2I); register_op>(Variant::OP_NOT_EQUAL, Variant::RECT2, Variant::RECT2); @@ -639,10 +649,6 @@ void Variant::_register_variant_operators() { register_op>(Variant::OP_NOT_EQUAL, Variant::PROJECTION, Variant::PROJECTION); register_op>(Variant::OP_NOT_EQUAL, Variant::COLOR, Variant::COLOR); - register_op>(Variant::OP_NOT_EQUAL, Variant::STRING_NAME, Variant::STRING); - register_op>(Variant::OP_NOT_EQUAL, Variant::STRING, Variant::STRING_NAME); - register_op>(Variant::OP_NOT_EQUAL, Variant::STRING_NAME, Variant::STRING_NAME); - register_op>(Variant::OP_NOT_EQUAL, Variant::NODE_PATH, Variant::NODE_PATH); register_op>(Variant::OP_NOT_EQUAL, Variant::RID, Variant::RID); @@ -895,10 +901,7 @@ void Variant::_register_variant_operators() { register_op(Variant::OP_NOT, Variant::FLOAT, Variant::NIL); register_op(Variant::OP_NOT, Variant::OBJECT, Variant::NIL); - register_op>(Variant::OP_IN, Variant::STRING, Variant::STRING); - register_op>(Variant::OP_IN, Variant::STRING_NAME, Variant::STRING); - register_op>(Variant::OP_IN, Variant::STRING, Variant::STRING_NAME); - register_op>(Variant::OP_IN, Variant::STRING_NAME, Variant::STRING_NAME); + register_string_op(OperatorEvaluatorInStringFind, Variant::OP_IN); register_op(Variant::OP_IN, Variant::NIL, Variant::DICTIONARY); register_op>(Variant::OP_IN, Variant::BOOL, Variant::DICTIONARY); @@ -996,6 +999,7 @@ void Variant::_register_variant_operators() { register_op>(Variant::OP_IN, Variant::FLOAT, Variant::PACKED_FLOAT64_ARRAY); register_op>(Variant::OP_IN, Variant::STRING, Variant::PACKED_STRING_ARRAY); + register_op>(Variant::OP_IN, Variant::STRING_NAME, Variant::PACKED_STRING_ARRAY); register_op>(Variant::OP_IN, Variant::VECTOR2, Variant::PACKED_VECTOR2_ARRAY); register_op>(Variant::OP_IN, Variant::VECTOR3, Variant::PACKED_VECTOR3_ARRAY); @@ -1006,6 +1010,9 @@ void Variant::_register_variant_operators() { register_op(Variant::OP_IN, Variant::STRING_NAME, Variant::OBJECT); } +#undef register_string_op +#undef register_string_modulo_op + void Variant::_unregister_variant_operators() { } diff --git a/core/variant/variant_op.h b/core/variant/variant_op.h index 34858540ec71..ea4216322ccb 100644 --- a/core/variant/variant_op.h +++ b/core/variant/variant_op.h @@ -875,7 +875,33 @@ class OperatorEvaluatorAppendArray { static Variant::Type get_return_type() { return GetTypeInfo>::VARIANT_TYPE; } }; -class OperatorEvaluatorStringModNil { +template +class OperatorEvaluatorStringConcat { +public: + static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { + const String a(*VariantGetInternalPtr::get_ptr(&p_left)); + const String b(*VariantGetInternalPtr::get_ptr(&p_right)); + *r_ret = a + b; + r_valid = true; + } + static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { + const String a(*VariantGetInternalPtr::get_ptr(left)); + const String b(*VariantGetInternalPtr::get_ptr(right)); + *VariantGetInternalPtr::get_ptr(r_ret) = a + b; + } + static void ptr_evaluate(const void *left, const void *right, void *r_ret) { + const String a(PtrToArg::convert(left)); + const String b(PtrToArg::convert(right)); + PtrToArg::encode(a + b, r_ret); + } + static Variant::Type get_return_type() { return Variant::STRING; } +}; + +template +class OperatorEvaluatorStringFormat; + +template +class OperatorEvaluatorStringFormat { public: _FORCE_INLINE_ static String do_mod(const String &s, bool *r_valid) { Array values; @@ -888,22 +914,22 @@ class OperatorEvaluatorStringModNil { return a; } static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - const String &a = *VariantGetInternalPtr::get_ptr(&p_left); - *r_ret = do_mod(a, &r_valid); + *r_ret = do_mod(*VariantGetInternalPtr::get_ptr(&p_left), &r_valid); } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { bool valid = true; - String result = do_mod(*VariantGetInternalPtr::get_ptr(left), &valid); + String result = do_mod(*VariantGetInternalPtr::get_ptr(left), &valid); ERR_FAIL_COND_MSG(!valid, result); *VariantGetInternalPtr::get_ptr(r_ret) = result; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { - PtrToArg::encode(do_mod(PtrToArg::convert(left), nullptr), r_ret); + PtrToArg::encode(do_mod(PtrToArg::convert(left), nullptr), r_ret); } static Variant::Type get_return_type() { return Variant::STRING; } }; -class OperatorEvaluatorStringModArray { +template +class OperatorEvaluatorStringFormat { public: _FORCE_INLINE_ static String do_mod(const String &s, const Array &p_values, bool *r_valid) { String a = s.sprintf(p_values, r_valid); @@ -913,22 +939,22 @@ class OperatorEvaluatorStringModArray { return a; } static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - const String &a = *VariantGetInternalPtr::get_ptr(&p_left); - *r_ret = do_mod(a, *VariantGetInternalPtr::get_ptr(&p_right), &r_valid); + *r_ret = do_mod(*VariantGetInternalPtr::get_ptr(&p_left), *VariantGetInternalPtr::get_ptr(&p_right), &r_valid); } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { bool valid = true; - String result = do_mod(*VariantGetInternalPtr::get_ptr(left), *VariantGetInternalPtr::get_ptr(right), &valid); + String result = do_mod(*VariantGetInternalPtr::get_ptr(left), *VariantGetInternalPtr::get_ptr(right), &valid); ERR_FAIL_COND_MSG(!valid, result); *VariantGetInternalPtr::get_ptr(r_ret) = result; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { - PtrToArg::encode(do_mod(PtrToArg::convert(left), PtrToArg::convert(right), nullptr), r_ret); + PtrToArg::encode(do_mod(PtrToArg::convert(left), PtrToArg::convert(right), nullptr), r_ret); } static Variant::Type get_return_type() { return Variant::STRING; } }; -class OperatorEvaluatorStringModObject { +template +class OperatorEvaluatorStringFormat { public: _FORCE_INLINE_ static String do_mod(const String &s, const Object *p_object, bool *r_valid) { Array values; @@ -941,23 +967,22 @@ class OperatorEvaluatorStringModObject { return a; } static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - const String &a = *VariantGetInternalPtr::get_ptr(&p_left); - *r_ret = do_mod(a, p_right.get_validated_object(), &r_valid); + *r_ret = do_mod(*VariantGetInternalPtr::get_ptr(&p_left), p_right.get_validated_object(), &r_valid); } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { bool valid = true; - String result = do_mod(*VariantGetInternalPtr::get_ptr(left), right->get_validated_object(), &valid); + String result = do_mod(*VariantGetInternalPtr::get_ptr(left), right->get_validated_object(), &valid); ERR_FAIL_COND_MSG(!valid, result); *VariantGetInternalPtr::get_ptr(r_ret) = result; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { - PtrToArg::encode(do_mod(PtrToArg::convert(left), PtrToArg::convert(right), nullptr), r_ret); + PtrToArg::encode(do_mod(PtrToArg::convert(left), PtrToArg::convert(right), nullptr), r_ret); } static Variant::Type get_return_type() { return Variant::STRING; } }; -template -class OperatorEvaluatorStringModT { +template +class OperatorEvaluatorStringFormat { public: _FORCE_INLINE_ static String do_mod(const String &s, const T &p_value, bool *r_valid) { Array values; @@ -969,17 +994,16 @@ class OperatorEvaluatorStringModT { return a; } static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - const String &a = *VariantGetInternalPtr::get_ptr(&p_left); - *r_ret = do_mod(a, *VariantGetInternalPtr::get_ptr(&p_right), &r_valid); + *r_ret = do_mod(*VariantGetInternalPtr::get_ptr(&p_left), *VariantGetInternalPtr::get_ptr(&p_right), &r_valid); } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { bool valid = true; - String result = do_mod(*VariantGetInternalPtr::get_ptr(left), *VariantGetInternalPtr::get_ptr(right), &valid); + String result = do_mod(*VariantGetInternalPtr::get_ptr(left), *VariantGetInternalPtr::get_ptr(right), &valid); ERR_FAIL_COND_MSG(!valid, result); *VariantGetInternalPtr::get_ptr(r_ret) = result; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { - PtrToArg::encode(do_mod(PtrToArg::convert(left), PtrToArg::convert(right), nullptr), r_ret); + PtrToArg::encode(do_mod(PtrToArg::convert(left), PtrToArg::convert(right), nullptr), r_ret); } static Variant::Type get_return_type() { return Variant::STRING; } }; @@ -1288,8 +1312,11 @@ class OperatorEvaluatorNotObject { //// +template +class OperatorEvaluatorInStringFind; + template -class OperatorEvaluatorInStringFind { +class OperatorEvaluatorInStringFind { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { const Left &str_a = *VariantGetInternalPtr::get_ptr(&p_left); @@ -1310,7 +1337,7 @@ class OperatorEvaluatorInStringFind { }; template -class OperatorEvaluatorInStringNameFind { +class OperatorEvaluatorInStringFind { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { const Left &str_a = *VariantGetInternalPtr::get_ptr(&p_left); diff --git a/doc/classes/String.xml b/doc/classes/String.xml index c186952c7446..61134afc2dbc 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -955,6 +955,12 @@ Appends [param right] at the end of this [String], also known as a string concatenation. + + + + + + diff --git a/doc/classes/StringName.xml b/doc/classes/StringName.xml index 02e9c62cd687..66a9490b5dbe 100644 --- a/doc/classes/StringName.xml +++ b/doc/classes/StringName.xml @@ -33,12 +33,815 @@ + + + + + Returns [code]true[/code] if the string begins with the given string. + + + + + + Returns an array containing the bigrams (pairs of consecutive letters) of this string. + [codeblock] + print("Bigrams".bigrams()) # Prints "[Bi, ig, gr, ra, am, ms]" + [/codeblock] + + + + + + Converts a string containing a binary number into an integer. Binary strings can either be prefixed with [code]0b[/code] or not, and they can also start with a [code]-[/code] before the optional prefix. + [codeblocks] + [gdscript] + print("0b101".bin_to_int()) # Prints "5". + print("101".bin_to_int()) # Prints "5". + [/gdscript] + [csharp] + GD.Print("0b101".BinToInt()); // Prints "5". + GD.Print("101".BinToInt()); // Prints "5". + [/csharp] + [/codeblocks] + + + + + + Returns a copy of the string with special characters escaped using the C language standard. + + + + + + Returns a copy of the string with escaped characters replaced by their meanings. Supported escape sequences are [code]\'[/code], [code]\"[/code], [code]\\[/code], [code]\a[/code], [code]\b[/code], [code]\f[/code], [code]\n[/code], [code]\r[/code], [code]\t[/code], [code]\v[/code]. + [b]Note:[/b] Unlike the GDScript parser, this method doesn't support the [code]\uXXXX[/code] escape sequence. + + + + + + Changes the case of some letters. Replaces underscores with spaces, adds spaces before in-word uppercase characters, converts all letters to lowercase, then capitalizes the first letter and every letter following a space character. For [code]capitalize camelCase mixed_with_underscores[/code], it will return [code]Capitalize Camel Case Mixed With Underscores[/code]. + + + + + + + Performs a case-sensitive comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. + [b]Behavior with different string lengths:[/b] Returns [code]1[/code] if the "base" string is longer than the [param to] string or [code]-1[/code] if the "base" string is shorter than the [param to] string. Keep in mind this length is determined by the number of Unicode codepoints, [i]not[/i] the actual visible characters. + [b]Behavior with empty strings:[/b] Returns [code]-1[/code] if the "base" string is empty, [code]1[/code] if the [param to] string is empty or [code]0[/code] if both strings are empty. + To get a boolean result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to] and [method naturalnocasecmp_to]. + + + + + + + Returns [code]true[/code] if the string contains the given string. + + + + + + + + + Returns the number of occurrences of substring [param what] between [param from] and [param to] positions. If [param from] and [param to] equals 0 the whole string will be used. If only [param to] equals 0 the remained substring will be used. + + + + + + + + + Returns the number of occurrences of substring [param what] (ignoring case) between [param from] and [param to] positions. If [param from] and [param to] equals 0 the whole string will be used. If only [param to] equals 0 the remained substring will be used. + + + + + + Returns a copy of the string with indentation (leading tabs and spaces) removed. See also [method indent] to add indentation. + + + + + + + Returns [code]true[/code] if the string ends with the given string. + + + + + + + + Returns the index of the [b]first[/b] case-sensitive occurrence of the specified string in this instance, or [code]-1[/code]. Optionally, the starting search index can be specified, continuing to the end of the string. + [b]Note:[/b] If you just want to know whether a string contains a substring, use the [code]in[/code] operator as follows: + [codeblocks] + [gdscript] + print("i" in "team") # Will print `false`. + [/gdscript] + [csharp] + // C# has no in operator, but we can use `Contains()`. + GD.Print("team".Contains("i")); // Will print `false`. + [/csharp] + [/codeblocks] + + + + + + + + Returns the index of the [b]first[/b] case-insensitive occurrence of the specified string in this instance, or [code]-1[/code]. Optionally, the starting search index can be specified, continuing to the end of the string. + + + + + + + + Formats the string by replacing all occurrences of [param placeholder] with the elements of [param values]. + [param values] can be a [Dictionary] or an [Array]. Any underscores in [param placeholder] will be replaced with the corresponding keys in advance. Array elements use their index as keys. + [codeblock] + # Prints: Waiting for Godot is a play by Samuel Beckett, and Godot Engine is named after it. + var use_array_values = "Waiting for {0} is a play by {1}, and {0} Engine is named after it." + print(use_array_values.format(["Godot", "Samuel Beckett"])) + + # Prints: User 42 is Godot. + print("User {id} is {name}.".format({"id": 42, "name": "Godot"})) + [/codeblock] + Some additional handling is performed when [param values] is an array. If [param placeholder] does not contain an underscore, the elements of the array will be used to replace one occurrence of the placeholder in turn; If an array element is another 2-element array, it'll be interpreted as a key-value pair. + [codeblock] + # Prints: User 42 is Godot. + print("User {} is {}.".format([42, "Godot"], "{}")) + print("User {id} is {name}.".format([["id", 42], ["name", "Godot"]])) + [/codeblock] + + + + + + If the string is a valid file path, returns the base directory name. + + + + + + If the string is a valid file path, returns the full file path without the extension. + + + + + + Returns the extension without the leading period character ([code].[/code]) if the string is a valid file name or path. If the string does not contain an extension, returns an empty string instead. + [codeblock] + print("/path/to/file.txt".get_extension()) # "txt" + print("file.txt".get_extension()) # "txt" + print("file.sample.txt".get_extension()) # "txt" + print(".txt".get_extension()) # "txt" + print("file.txt.".get_extension()) # "" (empty string) + print("file.txt..".get_extension()) # "" (empty string) + print("txt".get_extension()) # "" (empty string) + print("".get_extension()) # "" (empty string) + [/codeblock] + + + + + + If the string is a valid file path, returns the filename. + + + + + + + + Splits a string using a [param delimiter] and returns a substring at index [param slice]. Returns an empty string if the index doesn't exist. + This is a more performant alternative to [method split] for cases when you need only one element from the array at a fixed index. + [b]Example:[/b] + [codeblock] + print("i/am/example/string".get_slice("/", 2)) # Prints 'example'. + [/codeblock] + + + + + + + Splits a string using a [param delimiter] and returns a number of slices. + + + + + + + + Splits a string using a Unicode character with code [param delimiter] and returns a substring at index [param slice]. Returns an empty string if the index doesn't exist. + This is a more performant alternative to [method split] for cases when you need only one element from the array at a fixed index. + + Returns the 32-bit hash value representing the [StringName]'s contents. + + + + Converts a string containing a hexadecimal number into an integer. Hexadecimal strings can either be prefixed with [code]0x[/code] or not, and they can also start with a [code]-[/code] before the optional prefix. + [codeblocks] + [gdscript] + print("0xff".hex_to_int()) # Prints "255". + print("ab".hex_to_int()) # Prints "171". + [/gdscript] + [csharp] + GD.Print("0xff".HexToInt()); // Prints "255". + GD.Print("ab".HexToInt()); // Prints "171". + [/csharp] + [/codeblocks] + + + + + + + Returns a copy of the string with lines indented with [param prefix]. + For example, the string can be indented with two tabs using [code]"\t\t"[/code], or four spaces using [code]" "[/code]. The prefix can be any string so it can also be used to comment out strings with e.g. [code]"# "[/code]. See also [method dedent] to remove indentation. + [b]Note:[/b] Empty lines are kept empty. + + + + + + + + Returns a copy of the string with the substring [param what] inserted at the given [param position]. + + + + + + Returns [code]true[/code] if the string is a path to a file or directory and its starting point is explicitly defined. This includes [code]res://[/code], [code]user://[/code], [code]C:\[/code], [code]/[/code], etc. + + + + + + Returns [code]true[/code] if the length of the string equals [code]0[/code]. + + + + + + Returns [code]true[/code] if the string is a path to a file or directory and its starting point is implicitly defined within the context it is being used. The starting point may refer to the current directory ([code]./[/code]), or the current [Node]. + + + + + + + Returns [code]true[/code] if this string is a subsequence of the given string. + + + + + + + Returns [code]true[/code] if this string is a subsequence of the given string, without considering case. + + + + + + Returns [code]true[/code] if this string is free from characters that aren't allowed in file names, those being: + [code]: / \ ? * " | % < >[/code] + + + + + + Returns [code]true[/code] if this string contains a valid float. This is inclusive of integers, and also supports exponents: + [codeblock] + print("1.7".is_valid_float()) # Prints "true" + print("24".is_valid_float()) # Prints "true" + print("7e3".is_valid_float()) # Prints "true" + print("Hello".is_valid_float()) # Prints "false" + [/codeblock] + + + + + + + Returns [code]true[/code] if this string contains a valid hexadecimal number. If [param with_prefix] is [code]true[/code], then a validity of the hexadecimal number is determined by the [code]0x[/code] prefix, for example: [code]0xDEADC0DE[/code]. + + + + + + Returns [code]true[/code] if this string contains a valid color in hexadecimal HTML notation. Other HTML notations such as named colors or [code]hsl()[/code] colors aren't considered valid by this method and will return [code]false[/code]. + + + + + + Returns [code]true[/code] if this string is a valid identifier. A valid identifier may contain only letters, digits and underscores ([code]_[/code]) and the first character may not be a digit. + [codeblock] + print("good_ident_1".is_valid_identifier()) # Prints "true" + print("1st_bad_ident".is_valid_identifier()) # Prints "false" + print("bad_ident_#2".is_valid_identifier()) # Prints "false" + [/codeblock] + + + + + + Returns [code]true[/code] if this string contains a valid integer. + [codeblock] + print("7".is_valid_int()) # Prints "true" + print("14.6".is_valid_int()) # Prints "false" + print("L".is_valid_int()) # Prints "false" + print("+3".is_valid_int()) # Prints "true" + print("-12".is_valid_int()) # Prints "true" + [/codeblock] + + + + + + Returns [code]true[/code] if this string contains only a well-formatted IPv4 or IPv6 address. This method considers [url=https://en.wikipedia.org/wiki/Reserved_IP_addresses]reserved IP addresses[/url] such as [code]0.0.0.0[/code] as valid. + + + + + + + Returns a [String] which is the concatenation of the [param parts]. The separator between elements is the string providing this method. + [b]Example:[/b] + [codeblocks] + [gdscript] + print(", ".join(["One", "Two", "Three", "Four"])) + [/gdscript] + [csharp] + GD.Print(String.Join(",", new string[] {"One", "Two", "Three", "Four"})); + [/csharp] + [/codeblocks] + + + + + + Returns a copy of the string with special characters escaped using the JSON standard. + + + + + + + Returns a number of characters from the left of the string. If negative [param length] is used, the characters are counted downwards from [String]'s length. + [b]Example:[/b] + [codeblock] + print("sample text".left(3)) #prints "sam" + print("sample text".left(-3)) #prints "sample t" + [/codeblock] + + + + + + Returns the number of characters in the string. + + + + + + + + Formats a string to be at least [param min_length] long by adding [param character]s to the left of the string. + + + + + + + Returns a copy of the string with characters removed from the left. The [param chars] argument is a string specifying the set of characters to be removed. + [b]Note:[/b] The [param chars] is not a prefix. See [method trim_prefix] method that will remove a single prefix string rather than a set of characters. + + + + + + + Does a simple case-sensitive expression match, where [code]"*"[/code] matches zero or more arbitrary characters and [code]"?"[/code] matches any single character except a period ([code]"."[/code]). An empty string or empty expression always evaluates to [code]false[/code]. + + + + + + + Does a simple case-insensitive expression match, where [code]"*"[/code] matches zero or more arbitrary characters and [code]"?"[/code] matches any single character except a period ([code]"."[/code]). An empty string or empty expression always evaluates to [code]false[/code]. + + + + + + Returns the MD5 hash of the string as an array of bytes. + + + + + + Returns the MD5 hash of the string as a string. + + + + + + + Performs a case-insensitive [i]natural order[/i] comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. Internally, lowercase characters will be converted to uppercase during the comparison. + When used for sorting, natural order comparison will order suites of numbers as expected by most people. If you sort the numbers from 1 to 10 using natural order, you will get [code][1, 2, 3, ...][/code] instead of [code][1, 10, 2, 3, ...][/code]. + [b]Behavior with different string lengths:[/b] Returns [code]1[/code] if the "base" string is longer than the [param to] string or [code]-1[/code] if the "base" string is shorter than the [param to] string. Keep in mind this length is determined by the number of Unicode codepoints, [i]not[/i] the actual visible characters. + [b]Behavior with empty strings:[/b] Returns [code]-1[/code] if the "base" string is empty, [code]1[/code] if the [param to] string is empty or [code]0[/code] if both strings are empty. + To get a boolean result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to] and [method casecmp_to]. + + + + + + + Performs a case-insensitive comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. Internally, lowercase characters will be converted to uppercase during the comparison. + [b]Behavior with different string lengths:[/b] Returns [code]1[/code] if the "base" string is longer than the [param to] string or [code]-1[/code] if the "base" string is shorter than the [param to] string. Keep in mind this length is determined by the number of Unicode codepoints, [i]not[/i] the actual visible characters. + [b]Behavior with empty strings:[/b] Returns [code]-1[/code] if the "base" string is empty, [code]1[/code] if the [param to] string is empty or [code]0[/code] if both strings are empty. + To get a boolean result from a string comparison, use the [code]==[/code] operator instead. See also [method casecmp_to] and [method naturalnocasecmp_to]. + + + + + + + Formats a number to have an exact number of [param digits] after the decimal point. + + + + + + + Formats a number to have an exact number of [param digits] before the decimal point. + + + + + + + If the string is a path, this concatenates [param file] at the end of the string as a subpath. E.g. [code]"this/is".path_join("path") == "this/is/path"[/code]. + + + + + + + Returns original string repeated a number of times. The number of repetitions is given by the argument. + + + + + + + + Replaces occurrences of a case-sensitive substring with the given one inside the string. + + + + + + + + Replaces occurrences of a case-insensitive substring with the given one inside the string. + + + + + + + + Returns the index of the [b]last[/b] case-sensitive occurrence of the specified string in this instance, or [code]-1[/code]. Optionally, the starting search index can be specified, continuing to the beginning of the string. + + + + + + + + Returns the index of the [b]last[/b] case-insensitive occurrence of the specified string in this instance, or [code]-1[/code]. Optionally, the starting search index can be specified, continuing to the beginning of the string. + + + + + + + Returns a number of characters from the right of the string. If negative [param length] is used, the characters are counted downwards from [String]'s length. + [b]Example:[/b] + [codeblock] + print("sample text".right(3)) #prints "ext" + print("sample text".right(-3)) #prints "ple text" + [/codeblock] + + + + + + + + Formats a string to be at least [param min_length] long by adding [param character]s to the right of the string. + + + + + + + + + Splits the string by a [param delimiter] string and returns an array of the substrings, starting from right. If [param delimiter] is an empty string, each substring will be a single character. + The splits in the returned array are sorted in the same order as the original string, from left to right. + If [param allow_empty] is [code]true[/code], and there are two adjacent delimiters in the string, it will add an empty string to the array of substrings at this position. + If [param maxsplit] is specified, it defines the number of splits to do from the right up to [param maxsplit]. The default value of 0 means that all items are split, thus giving the same result as [method split]. + [b]Example:[/b] + [codeblocks] + [gdscript] + var some_string = "One,Two,Three,Four" + var some_array = some_string.rsplit(",", true, 1) + print(some_array.size()) # Prints 2 + print(some_array[0]) # Prints "One,Two,Three" + print(some_array[1]) # Prints "Four" + [/gdscript] + [csharp] + // There is no Rsplit. + [/csharp] + [/codeblocks] + + + + + + + Returns a copy of the string with characters removed from the right. The [param chars] argument is a string specifying the set of characters to be removed. + [b]Note:[/b] The [param chars] is not a suffix. See [method trim_suffix] method that will remove a single suffix string rather than a set of characters. + + + + + + Returns the SHA-1 hash of the string as an array of bytes. + + + + + + Returns the SHA-1 hash of the string as a string. + + + + + + Returns the SHA-256 hash of the string as an array of bytes. + + + + + + Returns the SHA-256 hash of the string as a string. + + + + + + + Returns the similarity index ([url=https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient]Sorensen-Dice coefficient[/url]) of this string compared to another. A result of 1.0 means totally similar, while 0.0 means totally dissimilar. + [codeblock] + print("ABC123".similarity("ABC123")) # Prints "1" + print("ABC123".similarity("XYZ456")) # Prints "0" + print("ABC123".similarity("123ABC")) # Prints "0.8" + print("ABC123".similarity("abc123")) # Prints "0.4" + [/codeblock] + + + + + + Returns a simplified canonical path. + + + + + + + + + Splits the string by a [param delimiter] string and returns an array of the substrings. The [param delimiter] can be of any length. If [param delimiter] is an empty string, each substring will be a single character. + If [param allow_empty] is [code]true[/code], and there are two adjacent delimiters in the string, it will add an empty string to the array of substrings at this position. + If [param maxsplit] is specified, it defines the number of splits to do from the left up to [param maxsplit]. The default value of [code]0[/code] means that all items are split. + If you need only one element from the array at a specific index, [method get_slice] is a more performant option. + [b]Example:[/b] + [codeblocks] + [gdscript] + var some_string = "One,Two,Three,Four" + var some_array = some_string.split(",", true, 1) + print(some_array.size()) # Prints 2 + print(some_array[0]) # Prints "Four" + print(some_array[1]) # Prints "Three,Two,One" + [/gdscript] + [csharp] + var someString = "One,Two,Three,Four"; + var someArray = someString.Split(",", true); // This is as close as it gets to Godots API. + GD.Print(someArray[0]); // Prints "Four" + GD.Print(someArray[1]); // Prints "Three,Two,One" + [/csharp] + [/codeblocks] + If you need to split strings with more complex rules, use the [RegEx] class instead. + + + + + + + + Splits the string in floats by using a delimiter string and returns an array of the substrings. + For example, [code]"1,2.5,3"[/code] will return [code][1,2.5,3][/code] if split by [code]","[/code]. + If [param allow_empty] is [code]true[/code], and there are two adjacent delimiters in the string, it will add an empty string to the array of substrings at this position. + + + + + + + + Returns a copy of the string stripped of any non-printable character (including tabulations, spaces and line breaks) at the beginning and the end. The optional arguments are used to toggle stripping on the left and right edges respectively. + + + + + + Returns a copy of the string stripped of any escape character. These include all non-printable control characters of the first page of the ASCII table (< 32), such as tabulation ([code]\t[/code] in C) and newline ([code]\n[/code] and [code]\r[/code]) characters, but not spaces. + + + + + + + + Returns part of the string from the position [param from] with length [param len]. Argument [param len] is optional and using [code]-1[/code] will return remaining characters from given position. + + + + + + Converts the String (which is a character array) to ASCII/Latin-1 encoded [PackedByteArray] (which is an array of bytes). The conversion is faster compared to [method to_utf8_buffer], as this method assumes that all the characters in the String are ASCII/Latin-1 characters, unsupported characters are replaced with spaces. + + + + + + Returns the string converted to [code]camelCase[/code]. + + + + + + Converts a string containing a decimal number into a [code]float[/code]. The method will stop on the first non-number character except the first [code].[/code] (decimal point), and [code]e[/code] which is used for exponential. + [codeblock] + print("12.3".to_float()) # 12.3 + print("1.2.3".to_float()) # 1.2 + print("12ab3".to_float()) # 12 + print("1e3".to_float()) # 1000 + [/codeblock] + + + + + + Converts a string containing an integer number into an [code]int[/code]. The method will remove any non-number character and stop if it encounters a [code].[/code]. + [codeblock] + print("123".to_int()) # 123 + print("a1b2c3".to_int()) # 123 + print("1.2.3".to_int()) # 1 + [/codeblock] + + + + + + Returns the string converted to lowercase. + + + + + + Returns the string converted to [code]PascalCase[/code]. + + + + + + Returns the string converted to [code]snake_case[/code]. + + + + + + Returns the string converted to uppercase. + + + + + + Converts the String (which is an array of characters) to UTF-16 encoded [PackedByteArray] (which is an array of bytes). + + + + + + Converts the String (which is an array of characters) to UTF-32 encoded [PackedByteArray] (which is an array of bytes). + + + + + + Converts the String (which is an array of characters) to UTF-8 encode [PackedByteArray] (which is an array of bytes). The conversion is a bit slower than [method to_ascii_buffer], but supports all UTF-8 characters. Therefore, you should prefer this function over [method to_ascii_buffer]. + + + + + + + Removes a given string from the start if it starts with it or leaves the string unchanged. + + + + + + + Removes a given string from the end if it ends with it or leaves the string unchanged. + + + + + + + Returns the character code at position [param at]. + + + + + + Decodes a string in URL encoded format. This is meant to decode parameters in a URL when receiving an HTTP request. + [codeblocks] + [gdscript] + print("https://example.org/?escaped=" + "Godot%20Engine%3A%27docs%27".uri_decode()) + [/gdscript] + [csharp] + GD.Print("https://example.org/?escaped=" + "Godot%20Engine%3a%27Docs%27".URIDecode()); + [/csharp] + [/codeblocks] + + + + + + Encodes a string to URL friendly format. This is meant to encode parameters in a URL when sending an HTTP request. + [codeblocks] + [gdscript] + print("https://example.org/?escaped=" + "Godot Engine:'docs'".uri_encode()) + [/gdscript] + [csharp] + GD.Print("https://example.org/?escaped=" + "Godot Engine:'docs'".URIEncode()); + [/csharp] + [/codeblocks] + + + + + + Removes any characters from the string that are prohibited in [Node] names ([code].[/code] [code]:[/code] [code]@[/code] [code]/[/code] [code]"[/code]). + + + + + + + Returns a copy of the string with special characters escaped using the XML standard. If [param escape_quotes] is [code]true[/code], the single quote ([code]'[/code]) and double quote ([code]"[/code]) characters are also escaped. + + + + + + Returns a copy of the string with escaped characters replaced by their meanings according to the XML standard. + + @@ -55,6 +858,24 @@ Returns [code]true[/code] if the [StringName] and [param right] do not refer to the same name. Comparisons between [StringName]s are much faster than regular [String] comparisons. + + + + + + + + + + + + + + + + + + diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index 14a2640e6399..65e73d8df448 100644 --- a/editor/doc_tools.cpp +++ b/editor/doc_tools.cpp @@ -701,7 +701,7 @@ void DocTools::generate(bool p_basic_types) { if (rt != Variant::NIL) { // Has operator. // Skip String % operator as it's registered separately for each Variant arg type, // we'll add it manually below. - if (i == Variant::STRING && Variant::Operator(j) == Variant::OP_MODULE) { + if ((i == Variant::STRING || i == Variant::STRING_NAME) && Variant::Operator(j) == Variant::OP_MODULE) { continue; } MethodInfo mi; @@ -718,7 +718,7 @@ void DocTools::generate(bool p_basic_types) { } } - if (i == Variant::STRING) { + if (i == Variant::STRING || i == Variant::STRING_NAME) { // We skipped % operator above, and we register it manually once for Variant arg type here. MethodInfo mi; mi.name = "operator %"; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 663d72038d18..ad37fe7ec0c1 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -2676,7 +2676,7 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { } void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) { - HashMap elements; + HashMap elements; for (int i = 0; i < p_dictionary->elements.size(); i++) { const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; @@ -3432,7 +3432,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::QUATERNION: case Variant::AABB: case Variant::OBJECT: - error = index_type.builtin_type != Variant::STRING; + error = index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME; break; // Expect String or number. case Variant::BASIS: @@ -3446,11 +3446,11 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::TRANSFORM3D: case Variant::PROJECTION: error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT && - index_type.builtin_type != Variant::STRING; + index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME; break; // Expect String or int. case Variant::COLOR: - error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING; + error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME; break; // Don't support indexing, but we will check it later. case Variant::RID: @@ -4164,6 +4164,8 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ if (p_target.kind == GDScriptParser::DataType::BUILTIN) { bool valid = p_source.kind == GDScriptParser::DataType::BUILTIN && p_target.builtin_type == p_source.builtin_type; + valid |= p_source.builtin_type == Variant::STRING && p_target.builtin_type == Variant::STRING_NAME; + valid |= p_source.builtin_type == Variant::STRING_NAME && p_target.builtin_type == Variant::STRING; if (!valid && p_allow_implicit_conversion) { valid = Variant::can_convert_strict(p_source.builtin_type, p_target.builtin_type); } diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 24241b712b9a..a9b5ad107c0f 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1252,9 +1252,30 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c equality_type.kind = GDScriptDataType::BUILTIN; equality_type.builtin_type = Variant::BOOL; + GDScriptCodeGenerator::Address type_string_addr = codegen.add_constant(Variant::STRING); + GDScriptCodeGenerator::Address type_string_name_addr = codegen.add_constant(Variant::STRING_NAME); + // Check type equality. GDScriptCodeGenerator::Address type_equality_addr = codegen.add_temporary(equality_type); codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_EQUAL, p_type_addr, literal_type_addr); + + // Check if StringName <-> String comparison is possible. + GDScriptCodeGenerator::Address type_comp_addr_1 = codegen.add_temporary(equality_type); + GDScriptCodeGenerator::Address type_comp_addr_2 = codegen.add_temporary(equality_type); + + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_EQUAL, p_type_addr, type_string_addr); + codegen.generator->write_binary_operator(type_comp_addr_2, Variant::OP_EQUAL, literal_type_addr, type_string_name_addr); + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_AND, type_comp_addr_1, type_comp_addr_2); + codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_OR, type_equality_addr, type_comp_addr_1); + + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_EQUAL, p_type_addr, type_string_name_addr); + codegen.generator->write_binary_operator(type_comp_addr_2, Variant::OP_EQUAL, literal_type_addr, type_string_addr); + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_AND, type_comp_addr_1, type_comp_addr_2); + codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_OR, type_equality_addr, type_comp_addr_1); + + codegen.generator->pop_temporary(); // Remove type_comp_addr_2 from stack. + codegen.generator->pop_temporary(); // Remove type_comp_addr_1 from stack. + codegen.generator->write_and_left_operand(type_equality_addr); // Get literal. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.gd new file mode 100644 index 000000000000..4dd2b556ee14 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.gd @@ -0,0 +1,9 @@ +# https://github.com/godotengine/godot/issues/62957 + +func test(): + var dict = { + &"key": "StringName", + "key": "String" + } + + print("Invalid dictionary: %s" % dict) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.out new file mode 100644 index 000000000000..189d8a79558b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Key "key" was already used in this dictionary (at line 5). diff --git a/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.gd new file mode 100644 index 000000000000..4511c3d10bab --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.gd @@ -0,0 +1,8 @@ +func test(): + # Converted to String when initialized + var string_array: Array[String] = [&"abc"] + print(string_array) + + # Converted to StringName when initialized + var stringname_array: Array[StringName] = ["abc"] + print(stringname_array) diff --git a/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.out new file mode 100644 index 000000000000..70dd01d88e27 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.out @@ -0,0 +1,3 @@ +GDTEST_OK +["abc"] +[&"abc"] diff --git a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd new file mode 100644 index 000000000000..5303fb04e2d2 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd @@ -0,0 +1,35 @@ +# https://github.com/godotengine/godot/issues/63965 + +func test(): + var array_str: Array = [] + array_str.push_back("godot") + print("StringName in Array: ", &"godot" in array_str) + + var array_sname: Array = [] + array_sname.push_back(&"godot") + print("String in Array: ", "godot" in array_sname) + + # Not equal because the values are different types. + print("Arrays not equal: ", array_str != array_sname) + + var string_array: Array[String] = [] + var stringname_array: Array[StringName] = [] + + assert(!string_array.push_back(&"abc")) + print("Array[String] insert converted: ", typeof(string_array[0]) == TYPE_STRING) + + assert(!stringname_array.push_back("abc")) + print("Array[StringName] insert converted: ", typeof(stringname_array[0]) == TYPE_STRING_NAME) + + print("StringName in Array[String]: ", &"abc" in string_array) + print("String in Array[StringName]: ", "abc" in stringname_array) + + var packed_string_array: PackedStringArray = [] + assert(!packed_string_array.push_back("abc")) + print("StringName in PackedStringArray: ", &"abc" in packed_string_array) + + assert(!string_array.push_back("abc")) + print("StringName finds String in Array: ", string_array.find(&"abc")) + + assert(!stringname_array.push_back(&"abc")) + print("String finds StringName in Array: ", stringname_array.find("abc")) diff --git a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.out new file mode 100644 index 000000000000..98ab78e8f1e8 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.out @@ -0,0 +1,11 @@ +GDTEST_OK +StringName in Array: true +String in Array: true +Arrays not equal: true +Array[String] insert converted: true +Array[StringName] insert converted: true +StringName in Array[String]: true +String in Array[StringName]: true +StringName in PackedStringArray: true +StringName finds String in Array: 0 +String finds StringName in Array: 0 diff --git a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd new file mode 100644 index 000000000000..1f15026f1789 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd @@ -0,0 +1,17 @@ +# https://github.com/godotengine/godot/issues/62957 + +func test(): + var string_dict = {} + string_dict["abc"] = 42 + var stringname_dict = {} + stringname_dict[&"abc"] = 24 + + print("String key is TYPE_STRING: ", typeof(string_dict.keys()[0]) == TYPE_STRING) + print("StringName key is TYPE_STRING: ", typeof(stringname_dict.keys()[0]) == TYPE_STRING) + + print("StringName gets String: ", string_dict.get(&"abc")) + print("String gets StringName: ", stringname_dict.get("abc")) + + stringname_dict[&"abc"] = 42 + # They compare equal because StringName keys are converted to String. + print("String Dictionary == StringName Dictionary: ", string_dict == stringname_dict) diff --git a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out new file mode 100644 index 000000000000..ab5b89d55c22 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out @@ -0,0 +1,6 @@ +GDTEST_OK +String key is TYPE_STRING: true +StringName key is TYPE_STRING: true +StringName gets String: 42 +String gets StringName: 24 +String Dictionary == StringName Dictionary: true diff --git a/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd new file mode 100644 index 000000000000..55be021a9060 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd @@ -0,0 +1,14 @@ +# https://github.com/godotengine/godot/issues/60145 + +func test(): + match "abc": + &"abc": + print("String matched StringName") + _: + print("no match") + + match &"abc": + "abc": + print("StringName matched String") + _: + print("no match") diff --git a/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out new file mode 100644 index 000000000000..9d5a18da3dc6 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out @@ -0,0 +1,3 @@ +GDTEST_OK +String matched StringName +StringName matched String diff --git a/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd new file mode 100644 index 000000000000..f8bd46523e43 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd @@ -0,0 +1,11 @@ +# https://github.com/godotengine/godot/issues/64171 + +func test(): + print("Compare ==: ", "abc" == &"abc") + print("Compare ==: ", &"abc" == "abc") + print("Compare !=: ", "abc" != &"abc") + print("Compare !=: ", &"abc" != "abc") + + print("Concat: ", "abc" + &"def") + print("Concat: ", &"abc" + "def") + print("Concat: ", &"abc" + &"def") diff --git a/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out new file mode 100644 index 000000000000..7e9c364b604a --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out @@ -0,0 +1,8 @@ +GDTEST_OK +Compare ==: true +Compare ==: true +Compare !=: false +Compare !=: false +Concat: abcdef +Concat: abcdef +Concat: abcdef diff --git a/modules/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp index dbf2b3751e92..a7e29dfcc7dd 100644 --- a/modules/multiplayer/scene_rpc_interface.cpp +++ b/modules/multiplayer/scene_rpc_interface.cpp @@ -82,7 +82,7 @@ void SceneRPCInterface::_parse_rpc_config(const Variant &p_config, bool p_for_no Array names = config.keys(); names.sort(); // Ensure ID order for (int i = 0; i < names.size(); i++) { - ERR_CONTINUE(names[i].get_type() != Variant::STRING); + ERR_CONTINUE(names[i].get_type() != Variant::STRING && names[i].get_type() != Variant::STRING_NAME); String name = names[i].operator String(); ERR_CONTINUE(config[name].get_type() != Variant::DICTIONARY); ERR_CONTINUE(!config[name].operator Dictionary().has("rpc_mode")); diff --git a/modules/regex/regex.cpp b/modules/regex/regex.cpp index c808211d68bd..6f02d20c2592 100644 --- a/modules/regex/regex.cpp +++ b/modules/regex/regex.cpp @@ -50,8 +50,7 @@ int RegExMatch::_find(const Variant &p_name) const { return -1; } return i; - - } else if (p_name.get_type() == Variant::STRING) { + } else if (p_name.get_type() == Variant::STRING || p_name.get_type() == Variant::STRING_NAME) { HashMap::ConstIterator found = names.find((String)p_name); if (found) { return found->value; diff --git a/tests/core/variant/test_dictionary.h b/tests/core/variant/test_dictionary.h index c98434d42c68..0c87f11ed70d 100644 --- a/tests/core/variant/test_dictionary.h +++ b/tests/core/variant/test_dictionary.h @@ -64,6 +64,19 @@ TEST_CASE("[Dictionary] Assignment using bracket notation ([])") { map["World!"] = 4; CHECK(int(map["World!"]) == 4); + map[StringName("HelloName")] = 6; + CHECK(int(map[StringName("HelloName")]) == 6); + // Check that StringName key is converted to String. + CHECK(int(map.find_key(6).get_type()) == Variant::STRING); + map[StringName("HelloName")] = 7; + CHECK(int(map[StringName("HelloName")]) == 7); + + // Test String and StringName are equivalent. + map[StringName("Hello")] = 8; + CHECK(int(map["Hello"]) == 8); + map["Hello"] = 9; + CHECK(int(map[StringName("Hello")]) == 9); + // Test non-string keys, since keys can be of any Variant type. map[12345] = -5; CHECK(int(map[12345]) == -5);