diff options
Diffstat (limited to 'modules/gdscript')
79 files changed, 1969 insertions, 506 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index e3f5502391..f2a65451a7 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="@GDScript" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Built-in GDScript functions. + Built-in GDScript constants, functions, and annotations. </brief_description> <description> A list of GDScript-specific utility functions and annotations accessible from any script. @@ -274,7 +274,7 @@ [b]Warning:[/b] Numeric infinity is only a concept with floating-point numbers, and has no equivalent for integers. Dividing an integer number by [code]0[/code] will not result in [constant INF] and will result in a run-time error instead. </constant> <constant name="NAN" value="nan"> - "Not a Number", an invalid floating-point value. [constant NAN] has special properties, including that it is not equal to itself ([code]NAN == NAN[/code] returns [code]false[/code]). It is output by some invalid operations, such as dividing floating-point [code]0.0[/code] by [code]0.0[/code]. + "Not a Number", an invalid floating-point value. [constant NAN] has special properties, including that [code]!=[/code] always returns [code]true[/code], while other comparison operators always return [code]false[/code]. This is true even when comparing with itself ([code]NAN == NAN[/code] returns [code]false[/code] and [code]NAN != NAN[/code] returns [code]true[/code]). It is returned by some invalid operations, such as dividing floating-point [code]0.0[/code] by [code]0.0[/code]. [b]Warning:[/b] "Not a Number" is only a concept with floating-point numbers, and has no equivalent for integers. Dividing an integer [code]0[/code] by [code]0[/code] will not result in [constant NAN] and will result in a run-time error instead. </constant> </constants> @@ -612,7 +612,7 @@ [/codeblock] </description> </annotation> - <annotation name="@rpc" qualifiers="vararg"> + <annotation name="@rpc"> <return type="void" /> <param index="0" name="mode" type="String" default=""authority"" /> <param index="1" name="sync" type="String" default=""call_remote"" /> @@ -620,7 +620,11 @@ <param index="3" name="transfer_channel" type="int" default="0" /> <description> Mark the following method for remote procedure calls. See [url=$DOCS_URL/tutorials/networking/high_level_multiplayer.html]High-level multiplayer[/url]. - The order of [param mode], [param sync] and [param transfer_mode] does not matter and all arguments can be omitted, but [param transfer_channel] always has to be the last argument. The accepted values for [param mode] are [code]"any_peer"[/code] or [code]"authority"[/code], for [param sync] are [code]"call_remote"[/code] or [code]"call_local"[/code] and for [param transfer_mode] are [code]"unreliable"[/code], [code]"unreliable_ordered"[/code] or [code]"reliable"[/code]. + The accepted values: + - for [param mode] are [code]"any_peer"[/code] or [code]"authority"[/code]; + - for [param sync] are [code]"call_remote"[/code] or [code]"call_local"[/code]; + - and for [param transfer_mode] are [code]"unreliable"[/code], [code]"unreliable_ordered"[/code], or [code]"reliable"[/code]. + The order of [param mode], [param sync] and [param transfer_mode] does not matter, but values related to the same argument must not be used more than once. [param transfer_channel] always has to be the 4th argument (you must specify 3 preceding arguments). [codeblock] @rpc func fn(): pass diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp index ce64d79747..df17581ad1 100644 --- a/modules/gdscript/editor/gdscript_docgen.cpp +++ b/modules/gdscript/editor/gdscript_docgen.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "gdscript_docgen.h" + #include "../gdscript.h" using GDP = GDScriptParser; diff --git a/modules/gdscript/editor/gdscript_docgen.h b/modules/gdscript/editor/gdscript_docgen.h index bb3647196a..3357fb680c 100644 --- a/modules/gdscript/editor/gdscript_docgen.h +++ b/modules/gdscript/editor/gdscript_docgen.h @@ -32,6 +32,7 @@ #define GDSCRIPT_DOCGEN_H #include "../gdscript_parser.h" + #include "core/doc_data.h" class GDScriptDocGen { diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index bba11363d5..b54dc502ae 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -29,8 +29,10 @@ /**************************************************************************/ #include "gdscript_highlighter.h" + #include "../gdscript.h" #include "../gdscript_tokenizer.h" + #include "core/config/project_settings.h" #include "editor/editor_settings.h" diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp index 00d50d1737..e17e748d7b 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp @@ -30,8 +30,9 @@ #include "gdscript_translation_parser_plugin.h" +#include "../gdscript.h" + #include "core/io/resource_loader.h" -#include "modules/gdscript/gdscript.h" void GDScriptEditorTranslationParserPlugin::get_recognized_extensions(List<String> *r_extensions) const { GDScriptLanguage::get_singleton()->get_recognized_extensions(r_extensions); @@ -311,6 +312,14 @@ void GDScriptEditorTranslationParserPlugin::_extract_from_call(GDScriptParser::C } } } + + if (p_call->callee && p_call->callee->type == GDScriptParser::Node::SUBSCRIPT) { + GDScriptParser::SubscriptNode *subscript_node = static_cast<GDScriptParser::SubscriptNode *>(p_call->callee); + if (subscript_node->base && subscript_node->base->type == GDScriptParser::Node::CALL) { + GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(subscript_node->base); + _extract_from_call(call_node); + } + } } void GDScriptEditorTranslationParserPlugin::_extract_fd_literals(GDScriptParser::ExpressionNode *p_expression) { diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h index 7e6e381e3f..421030e49a 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.h +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h @@ -31,9 +31,10 @@ #ifndef GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H #define GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H +#include "../gdscript_parser.h" + #include "core/templates/hash_set.h" #include "editor/editor_translation_parser.h" -#include "modules/gdscript/gdscript_parser.h" class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlugin { GDCLASS(GDScriptEditorTranslationParserPlugin, EditorTranslationParserPlugin); diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index d0790aba25..7a3b9dc597 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -30,15 +30,6 @@ #include "gdscript.h" -#include <stdint.h> - -#include "core/config/engine.h" -#include "core/config/project_settings.h" -#include "core/core_constants.h" -#include "core/core_string_names.h" -#include "core/io/file_access.h" -#include "core/io/file_access_encrypted.h" -#include "core/os/os.h" #include "gdscript_analyzer.h" #include "gdscript_cache.h" #include "gdscript_compiler.h" @@ -46,15 +37,28 @@ #include "gdscript_rpc_callable.h" #include "gdscript_warning.h" +#ifdef TOOLS_ENABLED +#include "editor/gdscript_docgen.h" +#endif + #ifdef TESTS_ENABLED #include "tests/gdscript_test_runner.h" #endif +#include "core/config/engine.h" +#include "core/config/project_settings.h" +#include "core/core_constants.h" +#include "core/core_string_names.h" +#include "core/io/file_access.h" +#include "core/io/file_access_encrypted.h" +#include "core/os/os.h" + #ifdef TOOLS_ENABLED #include "editor/editor_paths.h" -#include "editor/gdscript_docgen.h" #endif +#include <stdint.h> + /////////////////////////// GDScriptNativeClass::GDScriptNativeClass(const StringName &p_name) { @@ -874,44 +878,55 @@ Variant GDScript::callp(const StringName &p_method, const Variant **p_args, int } bool GDScript::_get(const StringName &p_name, Variant &r_ret) const { - { - const GDScript *top = this; - while (top) { - { - HashMap<StringName, Variant>::ConstIterator E = top->constants.find(p_name); - if (E) { - r_ret = E->value; - return true; - } + if (p_name == GDScriptLanguage::get_singleton()->strings._script_source) { + r_ret = get_source_code(); + return true; + } + + const GDScript *top = this; + while (top) { + { + HashMap<StringName, Variant>::ConstIterator E = top->constants.find(p_name); + if (E) { + r_ret = E->value; + return true; } + } - { - HashMap<StringName, Ref<GDScript>>::ConstIterator E = subclasses.find(p_name); - if (E) { - r_ret = E->value; + { + HashMap<StringName, MemberInfo>::ConstIterator E = top->static_variables_indices.find(p_name); + if (E) { + if (E->value.getter) { + Callable::CallError ce; + r_ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce); return true; } + r_ret = top->static_variables[E->value.index]; + return true; } + } - { - HashMap<StringName, MemberInfo>::ConstIterator E = static_variables_indices.find(p_name); - if (E) { - if (E->value.getter) { - Callable::CallError ce; - r_ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce); - return true; - } - r_ret = static_variables[E->value.index]; - return true; + { + HashMap<StringName, GDScriptFunction *>::ConstIterator E = top->member_functions.find(p_name); + if (E && E->value->is_static()) { + if (top->rpc_config.has(p_name)) { + r_ret = Callable(memnew(GDScriptRPCCallable(const_cast<GDScript *>(top), E->key))); + } else { + r_ret = Callable(const_cast<GDScript *>(top), E->key); } + return true; } - top = top->_base; } - if (p_name == GDScriptLanguage::get_singleton()->strings._script_source) { - r_ret = get_source_code(); - return true; + { + HashMap<StringName, Ref<GDScript>>::ConstIterator E = top->subclasses.find(p_name); + if (E) { + r_ret = E->value; + return true; + } } + + top = top->_base; } return false; @@ -921,40 +936,60 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) { if (p_name == GDScriptLanguage::get_singleton()->strings._script_source) { set_source_code(p_value); reload(); - } else { - const GDScript *top = this; - while (top) { - HashMap<StringName, MemberInfo>::ConstIterator E = static_variables_indices.find(p_name); - if (E) { - const GDScript::MemberInfo *member = &E->value; - Variant value = p_value; - if (member->data_type.has_type && !member->data_type.is_type(value)) { - const Variant *args = &p_value; - Callable::CallError err; - Variant::construct(member->data_type.builtin_type, value, &args, 1, err); - if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) { - return false; - } - } - if (member->setter) { - const Variant *args = &value; - Callable::CallError err; - callp(member->setter, &args, 1, err); - return err.error == Callable::CallError::CALL_OK; - } else { - static_variables.write[member->index] = value; - return true; + return true; + } + + GDScript *top = this; + while (top) { + HashMap<StringName, MemberInfo>::ConstIterator E = top->static_variables_indices.find(p_name); + if (E) { + const MemberInfo *member = &E->value; + Variant value = p_value; + if (member->data_type.has_type && !member->data_type.is_type(value)) { + const Variant *args = &p_value; + Callable::CallError err; + Variant::construct(member->data_type.builtin_type, value, &args, 1, err); + if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) { + return false; } } - top = top->_base; + if (member->setter) { + const Variant *args = &value; + Callable::CallError err; + callp(member->setter, &args, 1, err); + return err.error == Callable::CallError::CALL_OK; + } else { + top->static_variables.write[member->index] = value; + return true; + } } + + top = top->_base; } - return true; + return false; } void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const { p_properties->push_back(PropertyInfo(Variant::STRING, "script/source", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); + + List<PropertyInfo> property_list; + + const GDScript *top = this; + while (top) { + for (const KeyValue<StringName, MemberInfo> &E : top->static_variables_indices) { + PropertyInfo pi = PropertyInfo(E.value.data_type); + pi.name = E.key; + pi.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; // For the script (as a class) it is a non-static property. + property_list.push_back(pi); + } + + top = top->_base; + } + + for (const List<PropertyInfo>::Element *E = property_list.back(); E; E = E->prev()) { + p_properties->push_back(E->get()); + } } void GDScript::_bind_methods() { @@ -1033,6 +1068,16 @@ StringName GDScript::debug_get_member_by_index(int p_idx) const { return "<error>"; } +StringName GDScript::debug_get_static_var_by_index(int p_idx) const { + for (const KeyValue<StringName, MemberInfo> &E : static_variables_indices) { + if (E.value.index == p_idx) { + return E.key; + } + } + + return "<error>"; +} + Ref<GDScript> GDScript::get_base() const { return base; } @@ -1372,8 +1417,8 @@ void GDScript::clear(GDScript::ClearData *p_clear_data) { } clearing = true; - GDScript::ClearData data; - GDScript::ClearData *clear_data = p_clear_data; + ClearData data; + ClearData *clear_data = p_clear_data; bool is_root = false; // If `clear_data` is `nullptr`, it means that it's the root. @@ -1394,12 +1439,12 @@ void GDScript::clear(GDScript::ClearData *p_clear_data) { } member_functions.clear(); - for (KeyValue<StringName, GDScript::MemberInfo> &E : member_indices) { + for (KeyValue<StringName, MemberInfo> &E : member_indices) { clear_data->scripts.insert(E.value.data_type.script_type_ref); E.value.data_type.script_type_ref = Ref<Script>(); } - for (KeyValue<StringName, GDScript::MemberInfo> &E : static_variables_indices) { + for (KeyValue<StringName, MemberInfo> &E : static_variables_indices) { clear_data->scripts.insert(E.value.data_type.script_type_ref); E.value.data_type.script_type_ref = Ref<Script>(); } @@ -1482,7 +1527,6 @@ GDScript::~GDScript() { ////////////////////////////// bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { - //member { HashMap<StringName, GDScript::MemberInfo>::Iterator E = script->member_indices.find(p_name); if (E) { @@ -1510,17 +1554,45 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { GDScript *sptr = script.ptr(); while (sptr) { - HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._set); - if (E) { - Variant name = p_name; - const Variant *args[2] = { &name, &p_value }; + { + HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = sptr->static_variables_indices.find(p_name); + if (E) { + const GDScript::MemberInfo *member = &E->value; + Variant value = p_value; + if (member->data_type.has_type && !member->data_type.is_type(value)) { + const Variant *args = &p_value; + Callable::CallError err; + Variant::construct(member->data_type.builtin_type, value, &args, 1, err); + if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) { + return false; + } + } + if (member->setter) { + const Variant *args = &value; + Callable::CallError err; + callp(member->setter, &args, 1, err); + return err.error == Callable::CallError::CALL_OK; + } else { + sptr->static_variables.write[member->index] = value; + return true; + } + } + } - Callable::CallError err; - Variant ret = E->value->call(this, (const Variant **)args, 2, err); - if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) { - return true; + { + HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._set); + if (E) { + Variant name = p_name; + const Variant *args[2] = { &name, &p_value }; + + Callable::CallError err; + Variant ret = E->value->call(this, (const Variant **)args, 2, err); + if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) { + return true; + } } } + sptr = sptr->_base; } @@ -1528,62 +1600,69 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { } bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { + { + HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = script->member_indices.find(p_name); + if (E) { + if (E->value.getter) { + Callable::CallError err; + r_ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err); + if (err.error == Callable::CallError::CALL_OK) { + return true; + } + } + r_ret = members[E->value.index]; + return true; + } + } + const GDScript *sptr = script.ptr(); while (sptr) { { - HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = script->member_indices.find(p_name); + HashMap<StringName, Variant>::ConstIterator E = sptr->constants.find(p_name); + if (E) { + r_ret = E->value; + return true; + } + } + + { + HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = sptr->static_variables_indices.find(p_name); if (E) { if (E->value.getter) { - Callable::CallError err; - r_ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err); - if (err.error == Callable::CallError::CALL_OK) { - return true; - } + Callable::CallError ce; + r_ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce); + return true; } - r_ret = members[E->value.index]; - return true; //index found + r_ret = sptr->static_variables[E->value.index]; + return true; } } { - const GDScript *sl = sptr; - while (sl) { - HashMap<StringName, Variant>::ConstIterator E = sl->constants.find(p_name); - if (E) { - r_ret = E->value; - return true; //index found - } - sl = sl->_base; + HashMap<StringName, Vector<StringName>>::ConstIterator E = sptr->_signals.find(p_name); + if (E) { + r_ret = Signal(this->owner, E->key); + return true; } } { - // Signals. - const GDScript *sl = sptr; - while (sl) { - HashMap<StringName, Vector<StringName>>::ConstIterator E = sl->_signals.find(p_name); - if (E) { - r_ret = Signal(this->owner, E->key); - return true; //index found + HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(p_name); + if (E) { + if (sptr->rpc_config.has(p_name)) { + r_ret = Callable(memnew(GDScriptRPCCallable(this->owner, E->key))); + } else { + r_ret = Callable(this->owner, E->key); } - sl = sl->_base; + return true; } } { - // Methods. - const GDScript *sl = sptr; - while (sl) { - HashMap<StringName, GDScriptFunction *>::ConstIterator E = sl->member_functions.find(p_name); - if (E) { - if (sptr->rpc_config.has(p_name)) { - r_ret = Callable(memnew(GDScriptRPCCallable(this->owner, E->key))); - } else { - r_ret = Callable(this->owner, E->key); - } - return true; //index found - } - sl = sl->_base; + HashMap<StringName, Ref<GDScript>>::ConstIterator E = sptr->subclasses.find(p_name); + if (E) { + r_ret = E->value; + return true; } } @@ -2025,6 +2104,8 @@ void GDScriptLanguage::finish() { } s = s->next(); } + script_list.clear(); + function_list.clear(); } void GDScriptLanguage::profiling_start() { @@ -2590,20 +2671,12 @@ Ref<GDScript> GDScriptLanguage::get_script_by_fully_qualified_name(const String /*************** RESOURCE ***************/ Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { - if (r_error) { - *r_error = ERR_FILE_CANT_OPEN; - } - Error err; Ref<GDScript> scr = GDScriptCache::get_full_script(p_path, err, "", p_cache_mode == CACHE_MODE_IGNORE); - if (scr.is_null()) { - // Don't fail loading because of parsing error. - scr.instantiate(); - } - if (r_error) { - *r_error = OK; + // Don't fail loading because of parsing error. + *r_error = scr.is_valid() ? OK : err; } return scr; diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 60bd9eef53..9cf545c41d 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -31,6 +31,8 @@ #ifndef GDSCRIPT_H #define GDSCRIPT_H +#include "gdscript_function.h" + #include "core/debugger/engine_debugger.h" #include "core/debugger/script_debugger.h" #include "core/doc_data.h" @@ -38,7 +40,6 @@ #include "core/io/resource_saver.h" #include "core/object/script_language.h" #include "core/templates/rb_set.h" -#include "gdscript_function.h" class GDScriptNativeClass : public RefCounted { GDCLASS(GDScriptNativeClass, RefCounted); @@ -226,6 +227,7 @@ public: const HashMap<StringName, MemberInfo> &debug_get_member_indices() const { return member_indices; } const HashMap<StringName, GDScriptFunction *> &debug_get_member_functions() const; //this is debug only StringName debug_get_member_by_index(int p_idx) const; + StringName debug_get_static_var_by_index(int p_idx) const; Variant _new(const Variant **p_args, int p_argcount, Callable::CallError &r_error); virtual bool can_instantiate() const override; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 96bd8aafad..e1033c2aab 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -30,6 +30,9 @@ #include "gdscript_analyzer.h" +#include "gdscript.h" +#include "gdscript_utility_functions.h" + #include "core/config/engine.h" #include "core/config/project_settings.h" #include "core/core_constants.h" @@ -39,8 +42,6 @@ #include "core/object/class_db.h" #include "core/object/script_language.h" #include "core/templates/hash_map.h" -#include "gdscript.h" -#include "gdscript_utility_functions.h" #include "scene/resources/packed_scene.h" #if defined(TOOLS_ENABLED) && !defined(DISABLE_DEPRECATED) @@ -1610,11 +1611,10 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * GDScriptParser::DataType parent_return_type; List<GDScriptParser::DataType> parameters_types; int default_par_count = 0; - bool is_static = false; - bool is_vararg = false; + BitField<MethodFlags> method_flags; StringName native_base; - if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, is_static, is_vararg, &native_base)) { - bool valid = p_function->is_static == is_static; + if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, method_flags, &native_base)) { + bool valid = p_function->is_static == method_flags.has_flag(METHOD_FLAG_STATIC); valid = valid && parent_return_type == p_function->get_datatype(); int par_count_diff = p_function->parameters.size() - parameters_types.size(); @@ -1692,6 +1692,9 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun GDScriptParser::FunctionNode *previous_function = parser->current_function; parser->current_function = p_function; + bool previous_static_context = static_context; + static_context = p_function->is_static; + resolve_suite(p_function->body); if (!p_function->get_datatype().is_hard_type() && p_function->body->get_datatype().is_set()) { @@ -1707,6 +1710,7 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun parser->ignored_warnings = previously_ignored_warnings; #endif parser->current_function = previous_function; + static_context = previous_static_context; } void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement) { @@ -2025,9 +2029,8 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { GDScriptParser::DataType return_type; List<GDScriptParser::DataType> par_types; int default_arg_count = 0; - bool is_static = false; - bool is_vararg = false; - if (get_function_signature(p_for->list, false, list_type, CoreStringNames::get_singleton()->_iter_get, return_type, par_types, default_arg_count, is_static, is_vararg)) { + BitField<MethodFlags> method_flags; + if (get_function_signature(p_for->list, false, list_type, CoreStringNames::get_singleton()->_iter_get, return_type, par_types, default_arg_count, method_flags)) { variable_type = return_type; variable_type.type_source = list_type.type_source; } else if (!list_type.is_hard_type()) { @@ -2442,6 +2445,10 @@ void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNo continue; } if (!is_type_compatible(p_element_type, element_type, true, p_array)) { + if (is_type_compatible(element_type, p_element_type)) { + mark_node_unsafe(element_node); + continue; + } push_error(vformat(R"(Cannot have an element of type "%s" in an array of type "Array[%s]".)", element_type.to_string(), p_element_type.to_string()), element_node); return; } @@ -2462,9 +2469,15 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype(); - if (assignee_type.is_constant || (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->is_constant)) { + if (assignee_type.is_constant) { push_error("Cannot assign a new value to a constant.", p_assignment->assignee); return; + } else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->is_constant) { + const GDScriptParser::DataType &base_type = static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->datatype; + if (base_type.kind != GDScriptParser::DataType::SCRIPT && base_type.kind != GDScriptParser::DataType::CLASS) { // Static variables. + push_error("Cannot assign a new value to a constant.", p_assignment->assignee); + return; + } } else if (assignee_type.is_read_only) { push_error("Cannot assign a new value to a read-only property.", p_assignment->assignee); return; @@ -3076,15 +3089,24 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a return; } - bool is_static = false; - bool is_vararg = false; int default_arg_count = 0; + BitField<MethodFlags> method_flags; GDScriptParser::DataType return_type; List<GDScriptParser::DataType> par_types; bool is_constructor = (base_type.is_meta_type || (p_call->callee && p_call->callee->type == GDScriptParser::Node::IDENTIFIER)) && p_call->function_name == SNAME("new"); - if (get_function_signature(p_call, is_constructor, base_type, p_call->function_name, return_type, par_types, default_arg_count, is_static, is_vararg)) { + if (get_function_signature(p_call, is_constructor, base_type, p_call->function_name, return_type, par_types, default_arg_count, method_flags)) { + // If the method is implemented in the class hierarchy, the virtual flag will not be set for that MethodInfo and the search stops there. + // MethodInfo's above the class that defines the method might still have the virtual flag set. + if (method_flags.has_flag(METHOD_FLAG_VIRTUAL)) { + if (p_call->is_super) { + push_error(vformat(R"*(Cannot call the parent class' virtual function "%s()" because it hasn't been defined.)*", p_call->function_name), p_call); + } else { + push_error(vformat(R"*(Cannot call virtual function "%s()" because it hasn't been defined.)*", p_call->function_name), p_call); + } + } + // If the function require typed arrays we must make literals be typed. for (const KeyValue<int, GDScriptParser::ArrayNode *> &E : arrays) { int index = E.key; @@ -3092,14 +3114,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a update_array_literal_element_type(E.value, par_types[index].get_container_element_type()); } } - validate_call_arg(par_types, default_arg_count, is_vararg, p_call); + validate_call_arg(par_types, default_arg_count, method_flags.has_flag(METHOD_FLAG_VARARG), p_call); if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) { // Enum type is treated as a dictionary value for function calls. base_type.is_meta_type = false; } - if (is_self && static_context && !is_static) { + if (is_self && static_context && !method_flags.has_flag(METHOD_FLAG_STATIC)) { if (parser->current_function) { // Get the parent function above any lambda. GDScriptParser::FunctionNode *parent_function = parser->current_function; @@ -3110,10 +3132,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } else { push_error(vformat(R"*(Cannot call non-static function "%s()" for static variable initializer.)*", p_call->function_name), p_call); } - } else if (!is_self && base_type.is_meta_type && !is_static) { + } else if (!is_self && base_type.is_meta_type && !method_flags.has_flag(METHOD_FLAG_STATIC)) { base_type.is_meta_type = false; // For `to_string()`. push_error(vformat(R"*(Cannot call non-static function "%s()" on the class "%s" directly. Make an instance instead.)*", p_call->function_name, base_type.to_string()), p_call); - } else if (is_self && !is_static) { + } else if (is_self && !method_flags.has_flag(METHOD_FLAG_STATIC)) { mark_lambda_use_self(); } @@ -3122,11 +3144,12 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } #ifdef DEBUG_ENABLED - if (p_is_root && return_type.kind != GDScriptParser::DataType::UNRESOLVED && return_type.builtin_type != Variant::NIL) { + if (p_is_root && return_type.kind != GDScriptParser::DataType::UNRESOLVED && return_type.builtin_type != Variant::NIL && + !(p_call->is_super && p_call->function_name == GDScriptLanguage::get_singleton()->strings._init)) { parser->push_warning(p_call, GDScriptWarning::RETURN_VALUE_DISCARDED, p_call->function_name); } - if (is_static && !is_constructor && !base_type.is_meta_type && !(is_self && static_context)) { + if (method_flags.has_flag(METHOD_FLAG_STATIC) && !is_constructor && !base_type.is_meta_type && !(is_self && static_context)) { String caller_type = String(base_type.native_type); if (caller_type.is_empty()) { @@ -3349,7 +3372,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod base = *p_base; } - const StringName &name = p_identifier->name; + StringName name = p_identifier->name; if (base.kind == GDScriptParser::DataType::ENUM) { if (base.is_meta_type) { @@ -3444,12 +3467,18 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod get_class_node_current_scope_classes(base_class, &script_classes); } + bool is_constructor = base.is_meta_type && p_identifier->name == SNAME("new"); + for (GDScriptParser::ClassNode *script_class : script_classes) { if (p_base == nullptr && script_class->identifier && script_class->identifier->name == name) { reduce_identifier_from_base_set_class(p_identifier, script_class->get_datatype()); return; } + if (is_constructor) { + name = "_init"; + } + if (script_class->has_member(name)) { resolve_class_member(script_class, name, p_identifier); @@ -3499,7 +3528,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } break; case GDScriptParser::ClassNode::Member::FUNCTION: { - if (is_base && !base.is_meta_type) { + if (is_base && (!base.is_meta_type || member.function->is_static)) { p_identifier->set_datatype(make_callable_type(member.function->info)); return; } @@ -3528,6 +3557,10 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod const StringName &native = base.native_type; if (class_exists(native)) { + if (is_constructor) { + name = "_init"; + } + MethodInfo method_info; if (ClassDB::has_property(native, name)) { StringName getter_name = ClassDB::get_property_getter(native, name); @@ -4642,9 +4675,8 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo return result; } -bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg, StringName *r_native_class) { - r_static = false; - r_vararg = false; +bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags, StringName *r_native_class) { + r_method_flags = METHOD_FLAGS_DEFAULT; r_default_arg_count = 0; if (r_native_class) { *r_native_class = StringName(); @@ -4677,10 +4709,9 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo for (const MethodInfo &E : methods) { if (E.name == p_function) { - function_signature_from_info(E, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); - r_static = Variant::is_builtin_method_static(p_base_type.builtin_type, function_name); + function_signature_from_info(E, r_return_type, r_par_types, r_default_arg_count, r_method_flags); // Cannot use non-const methods on enums. - if (!r_static && was_enum && !(E.flags & METHOD_FLAG_CONST)) { + if (!r_method_flags.has_flag(METHOD_FLAG_STATIC) && was_enum && !(E.flags & METHOD_FLAG_CONST)) { push_error(vformat(R"*(Cannot call non-const Dictionary function "%s()" on enum "%s".)*", p_function, p_base_type.enum_type), p_source); } return true; @@ -4710,8 +4741,8 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo } if (p_is_constructor) { - function_name = "_init"; - r_static = true; + function_name = GDScriptLanguage::get_singleton()->strings._init; + r_method_flags.set_flag(METHOD_FLAG_STATIC); } GDScriptParser::ClassNode *base_class = p_base_type.class_type; @@ -4734,7 +4765,9 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo } if (found_function != nullptr) { - r_static = p_is_constructor || found_function->is_static; + if (p_is_constructor || found_function->is_static) { + r_method_flags.set_flag(METHOD_FLAG_STATIC); + } for (int i = 0; i < found_function->parameters.size(); i++) { r_par_types.push_back(found_function->parameters[i]->get_datatype()); if (found_function->parameters[i]->initializer != nullptr) { @@ -4754,7 +4787,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo MethodInfo info = base_script->get_method_info(function_name); if (!(info == MethodInfo())) { - return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_method_flags); } base_script = base_script->get_base_script(); } @@ -4765,7 +4798,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo StringName script_class = p_base_type.kind == GDScriptParser::DataType::SCRIPT ? p_base_type.script_type->get_class_name() : StringName(GDScript::get_class_static()); if (ClassDB::get_method_info(script_class, function_name, &info)) { - return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_method_flags); } } @@ -4779,9 +4812,9 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo MethodInfo info; if (ClassDB::get_method_info(base_native, function_name, &info)) { - bool valid = function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + bool valid = function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_method_flags); if (valid && Engine::get_singleton()->has_singleton(base_native)) { - r_static = true; + r_method_flags.set_flag(METHOD_FLAG_STATIC); } #ifdef DEBUG_ENABLED MethodBind *native_method = ClassDB::get_method(base_native, function_name); @@ -4795,11 +4828,10 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo return false; } -bool GDScriptAnalyzer::function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) { +bool GDScriptAnalyzer::function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags) { r_return_type = type_from_property(p_info.return_val); r_default_arg_count = p_info.default_arguments.size(); - r_vararg = (p_info.flags & METHOD_FLAG_VARARG) != 0; - r_static = (p_info.flags & METHOD_FLAG_STATIC) != 0; + r_method_flags = p_info.flags; for (const PropertyInfo &E : p_info.arguments) { r_par_types.push_back(type_from_property(E, true)); @@ -4929,6 +4961,17 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator } GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source) { + if (p_operation == Variant::OP_AND || p_operation == Variant::OP_OR) { + // Those work for any type of argument and always return a boolean. + // They don't use the Variant operator since they have short-circuit semantics. + r_valid = true; + GDScriptParser::DataType result; + result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + result.kind = GDScriptParser::DataType::BUILTIN; + result.builtin_type = Variant::BOOL; + return result; + } + Variant::Type a_type = p_a.builtin_type; Variant::Type b_type = p_b.builtin_type; diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 0c7bf4125b..195e23b503 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -31,11 +31,12 @@ #ifndef GDSCRIPT_ANALYZER_H #define GDSCRIPT_ANALYZER_H +#include "gdscript_cache.h" +#include "gdscript_parser.h" + #include "core/object/object.h" #include "core/object/ref_counted.h" #include "core/templates/hash_set.h" -#include "gdscript_cache.h" -#include "gdscript_parser.h" class GDScriptAnalyzer { GDScriptParser *parser = nullptr; @@ -115,8 +116,8 @@ class GDScriptAnalyzer { static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type); GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const; GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source); - bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg, StringName *r_native_class = nullptr); - bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); + bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags, StringName *r_native_class = nullptr); + bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, BitField<MethodFlags> &r_method_flags); void validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call); void validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call); GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 1414075ba8..47cd3f768b 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -30,9 +30,10 @@ #include "gdscript_byte_codegen.h" -#include "core/debugger/engine_debugger.h" #include "gdscript.h" +#include "core/debugger/engine_debugger.h" + uint32_t GDScriptByteCodeGenerator::add_parameter(const StringName &p_name, bool p_is_optional, const GDScriptDataType &p_type) { #ifdef TOOLS_ENABLED function->arg_names.push_back(p_name); @@ -852,6 +853,20 @@ void GDScriptByteCodeGenerator::write_get_member(const Address &p_target, const append(p_name); } +void GDScriptByteCodeGenerator::write_set_static_variable(const Address &p_value, const Address &p_class, int p_index) { + append_opcode(GDScriptFunction::OPCODE_SET_STATIC_VARIABLE); + append(p_value); + append(p_class); + append(p_index); +} + +void GDScriptByteCodeGenerator::write_get_static_variable(const Address &p_target, const Address &p_class, int p_index) { + append_opcode(GDScriptFunction::OPCODE_GET_STATIC_VARIABLE); + append(p_target); + append(p_class); + append(p_index); +} + void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_target, const Address &p_source) { switch (p_target.type.kind) { case GDScriptDataType::BUILTIN: { diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index fc684e4d8f..bbcd252b13 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -32,7 +32,6 @@ #define GDSCRIPT_BYTE_CODEGEN_H #include "gdscript_codegen.h" - #include "gdscript_function.h" #include "gdscript_utility_functions.h" @@ -366,8 +365,6 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { return p_address.address | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); case Address::CONSTANT: return p_address.address | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); - case Address::STATIC_VARIABLE: - return p_address.address | (GDScriptFunction::ADDR_TYPE_STATIC_VAR << GDScriptFunction::ADDR_BITS); case Address::LOCAL_VARIABLE: case Address::FUNCTION_PARAMETER: return p_address.address | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); @@ -503,6 +500,8 @@ public: virtual void write_get_named(const Address &p_target, const StringName &p_name, const Address &p_source) override; virtual void write_set_member(const Address &p_value, const StringName &p_name) override; virtual void write_get_member(const Address &p_target, const StringName &p_name) override; + virtual void write_set_static_variable(const Address &p_value, const Address &p_class, int p_index) override; + virtual void write_get_static_variable(const Address &p_target, const Address &p_class, int p_index) override; virtual void write_assign(const Address &p_target, const Address &p_source) override; virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) override; virtual void write_assign_true(const Address &p_target) override; diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 126fccbbf0..e5bb93e3c8 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -30,12 +30,13 @@ #include "gdscript_cache.h" -#include "core/io/file_access.h" -#include "core/templates/vector.h" #include "gdscript.h" #include "gdscript_analyzer.h" #include "gdscript_compiler.h" #include "gdscript_parser.h" + +#include "core/io/file_access.h" +#include "core/templates/vector.h" #include "scene/resources/packed_scene.h" bool GDScriptParserRef::is_valid() const { @@ -253,7 +254,11 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_e Ref<GDScript> script; script.instantiate(); script->set_path(p_path, true); - script->load_source_code(p_path); + r_error = script->load_source_code(p_path); + + if (r_error) { + return Ref<GDScript>(); // Returns null and does not cache when the script fails to load. + } Ref<GDScriptParserRef> parser_ref = get_parser(p_path, GDScriptParserRef::PARSED, r_error); if (r_error == OK) { diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index 28266a1c0b..0a0f403e44 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -31,11 +31,12 @@ #ifndef GDSCRIPT_CACHE_H #define GDSCRIPT_CACHE_H +#include "gdscript.h" + #include "core/object/ref_counted.h" #include "core/os/mutex.h" #include "core/templates/hash_map.h" #include "core/templates/hash_set.h" -#include "gdscript.h" #include "scene/resources/packed_scene.h" class GDScriptAnalyzer; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index dbc2466393..9810f5395a 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -31,11 +31,12 @@ #ifndef GDSCRIPT_CODEGEN_H #define GDSCRIPT_CODEGEN_H -#include "core/string/string_name.h" -#include "core/variant/variant.h" #include "gdscript_function.h" #include "gdscript_utility_functions.h" +#include "core/string/string_name.h" +#include "core/variant/variant.h" + class GDScriptCodeGenerator { public: struct Address { @@ -44,7 +45,6 @@ public: CLASS, MEMBER, CONSTANT, - STATIC_VARIABLE, LOCAL_VARIABLE, FUNCTION_PARAMETER, TEMPORARY, @@ -110,6 +110,8 @@ public: virtual void write_get_named(const Address &p_target, const StringName &p_name, const Address &p_source) = 0; virtual void write_set_member(const Address &p_value, const StringName &p_name) = 0; virtual void write_get_member(const Address &p_target, const StringName &p_name) = 0; + virtual void write_set_static_variable(const Address &p_value, const Address &p_class, int p_index) = 0; + virtual void write_get_static_variable(const Address &p_target, const Address &p_class, int p_index) = 0; virtual void write_assign(const Address &p_target, const Address &p_source) = 0; virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) = 0; virtual void write_assign_true(const Address &p_target) = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 327e24ef11..90ee56b9de 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -262,18 +262,27 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } // Try static variables. - if (codegen.script->static_variables_indices.has(identifier)) { - if (codegen.script->static_variables_indices[identifier].getter != StringName() && codegen.script->static_variables_indices[identifier].getter != codegen.function_name) { - // Perform getter. - GDScriptCodeGenerator::Address temp = codegen.add_temporary(codegen.script->static_variables_indices[identifier].data_type); - GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS); - Vector<GDScriptCodeGenerator::Address> args; // No argument needed. - gen->write_call(temp, class_addr, codegen.script->static_variables_indices[identifier].getter, args); - return temp; - } else { - // No getter or inside getter: direct variable access. - int idx = codegen.script->static_variables_indices[identifier].index; - return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::STATIC_VARIABLE, idx, codegen.script->static_variables_indices[identifier].data_type); + { + GDScript *scr = codegen.script; + while (scr) { + if (scr->static_variables_indices.has(identifier)) { + if (scr->static_variables_indices[identifier].getter != StringName() && scr->static_variables_indices[identifier].getter != codegen.function_name) { + // Perform getter. + GDScriptCodeGenerator::Address temp = codegen.add_temporary(scr->static_variables_indices[identifier].data_type); + GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS); + Vector<GDScriptCodeGenerator::Address> args; // No argument needed. + gen->write_call(temp, class_addr, scr->static_variables_indices[identifier].getter, args); + return temp; + } else { + // No getter or inside getter: direct variable access. + GDScriptCodeGenerator::Address temp = codegen.add_temporary(scr->static_variables_indices[identifier].data_type); + GDScriptCodeGenerator::Address _class = codegen.add_constant(scr); + int index = scr->static_variables_indices[identifier].index; + gen->write_get_static_variable(temp, _class, index); + return temp; + } + } + scr = scr->_base; } } @@ -926,6 +935,10 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code bool member_property_has_setter = false; bool member_property_is_in_setter = false; bool is_static = false; + GDScriptCodeGenerator::Address static_var_class; + int static_var_index = 0; + GDScriptDataType static_var_data_type; + StringName var_name; StringName member_property_setter_function; List<const GDScriptParser::SubscriptNode *> chain; @@ -939,19 +952,39 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Check for a property. if (n->base->type == GDScriptParser::Node::IDENTIFIER) { GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(n->base); - StringName var_name = identifier->name; + var_name = identifier->name; if (_is_class_member_property(codegen, var_name)) { assign_class_member_property = var_name; - } else if (!_is_local_or_parameter(codegen, var_name) && (codegen.script->member_indices.has(var_name) || codegen.script->static_variables_indices.has(var_name))) { - is_member_property = true; - is_static = codegen.script->static_variables_indices.has(var_name); - const GDScript::MemberInfo &minfo = is_static ? codegen.script->static_variables_indices[var_name] : codegen.script->member_indices[var_name]; - member_property_setter_function = minfo.setter; - member_property_has_setter = member_property_setter_function != StringName(); - member_property_is_in_setter = member_property_has_setter && member_property_setter_function == codegen.function_name; - target_member_property.mode = is_static ? GDScriptCodeGenerator::Address::STATIC_VARIABLE : GDScriptCodeGenerator::Address::MEMBER; - target_member_property.address = minfo.index; - target_member_property.type = minfo.data_type; + } else if (!_is_local_or_parameter(codegen, var_name)) { + if (codegen.script->member_indices.has(var_name)) { + is_member_property = true; + is_static = false; + const GDScript::MemberInfo &minfo = codegen.script->member_indices[var_name]; + member_property_setter_function = minfo.setter; + member_property_has_setter = member_property_setter_function != StringName(); + member_property_is_in_setter = member_property_has_setter && member_property_setter_function == codegen.function_name; + target_member_property.mode = GDScriptCodeGenerator::Address::MEMBER; + target_member_property.address = minfo.index; + target_member_property.type = minfo.data_type; + } else { + // Try static variables. + GDScript *scr = codegen.script; + while (scr) { + if (scr->static_variables_indices.has(var_name)) { + is_member_property = true; + is_static = true; + const GDScript::MemberInfo &minfo = scr->static_variables_indices[var_name]; + member_property_setter_function = minfo.setter; + member_property_has_setter = member_property_setter_function != StringName(); + member_property_is_in_setter = member_property_has_setter && member_property_setter_function == codegen.function_name; + static_var_class = codegen.add_constant(scr); + static_var_index = minfo.index; + static_var_data_type = minfo.data_type; + break; + } + scr = scr->_base; + } + } } } break; @@ -1104,8 +1137,13 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code if (member_property_has_setter && !member_property_is_in_setter) { Vector<GDScriptCodeGenerator::Address> args; args.push_back(assigned); - GDScriptCodeGenerator::Address self = is_static ? GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS) : GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF); - gen->write_call(GDScriptCodeGenerator::Address(), self, member_property_setter_function, args); + GDScriptCodeGenerator::Address call_base = is_static ? GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS) : GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF); + gen->write_call(GDScriptCodeGenerator::Address(), call_base, member_property_setter_function, args); + } else if (is_static) { + GDScriptCodeGenerator::Address temp = codegen.add_temporary(static_var_data_type); + gen->write_assign(temp, assigned); + gen->write_set_static_variable(temp, static_var_class, static_var_index); + gen->pop_temporary(); } else { gen->write_assign(target_member_property, assigned); } @@ -1155,18 +1193,42 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code bool has_setter = false; bool is_in_setter = false; bool is_static = false; + GDScriptCodeGenerator::Address static_var_class; + int static_var_index = 0; + GDScriptDataType static_var_data_type; + StringName var_name; StringName setter_function; - StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name; - if (!_is_local_or_parameter(codegen, var_name) && (codegen.script->member_indices.has(var_name) || codegen.script->static_variables_indices.has(var_name))) { - is_member = true; - is_static = codegen.script->static_variables_indices.has(var_name); - GDScript::MemberInfo &minfo = is_static ? codegen.script->static_variables_indices[var_name] : codegen.script->member_indices[var_name]; - setter_function = minfo.setter; - has_setter = setter_function != StringName(); - is_in_setter = has_setter && setter_function == codegen.function_name; - member.mode = is_static ? GDScriptCodeGenerator::Address::STATIC_VARIABLE : GDScriptCodeGenerator::Address::MEMBER; - member.address = minfo.index; - member.type = minfo.data_type; + var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name; + if (!_is_local_or_parameter(codegen, var_name)) { + if (codegen.script->member_indices.has(var_name)) { + is_member = true; + is_static = false; + GDScript::MemberInfo &minfo = codegen.script->member_indices[var_name]; + setter_function = minfo.setter; + has_setter = setter_function != StringName(); + is_in_setter = has_setter && setter_function == codegen.function_name; + member.mode = GDScriptCodeGenerator::Address::MEMBER; + member.address = minfo.index; + member.type = minfo.data_type; + } else { + // Try static variables. + GDScript *scr = codegen.script; + while (scr) { + if (scr->static_variables_indices.has(var_name)) { + is_member = true; + is_static = true; + GDScript::MemberInfo &minfo = scr->static_variables_indices[var_name]; + setter_function = minfo.setter; + has_setter = setter_function != StringName(); + is_in_setter = has_setter && setter_function == codegen.function_name; + static_var_class = codegen.add_constant(scr); + static_var_index = minfo.index; + static_var_data_type = minfo.data_type; + break; + } + scr = scr->_base; + } + } } GDScriptCodeGenerator::Address target; @@ -1200,13 +1262,21 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code to_assign = assigned_value; } - GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype(), codegen.script); - if (has_setter && !is_in_setter) { // Call setter. Vector<GDScriptCodeGenerator::Address> args; args.push_back(to_assign); - gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), setter_function, args); + GDScriptCodeGenerator::Address call_base = is_static ? GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS) : GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF); + gen->write_call(GDScriptCodeGenerator::Address(), call_base, setter_function, args); + } else if (is_static) { + GDScriptCodeGenerator::Address temp = codegen.add_temporary(static_var_data_type); + if (assignment->use_conversion_assign) { + gen->write_assign_with_conversion(temp, to_assign); + } else { + gen->write_assign(temp, to_assign); + } + gen->write_set_static_variable(temp, static_var_class, static_var_index); + gen->pop_temporary(); } else { // Just assign. if (assignment->use_conversion_assign) { @@ -1272,7 +1342,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c } // Get literal type into constant map. - GDScriptCodeGenerator::Address literal_type_addr = codegen.add_constant((int)p_pattern->literal->value.get_type()); + Variant::Type literal_type = p_pattern->literal->value.get_type(); + GDScriptCodeGenerator::Address literal_type_addr = codegen.add_constant(literal_type); // Equality is always a boolean. GDScriptDataType equality_type; @@ -1280,29 +1351,31 @@ 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); + if (literal_type == Variant::STRING) { + GDScriptCodeGenerator::Address type_stringname_addr = codegen.add_constant(Variant::STRING_NAME); + + // Check StringName <-> String type equality. + GDScriptCodeGenerator::Address tmp_comp_addr = codegen.add_temporary(equality_type); + + codegen.generator->write_binary_operator(tmp_comp_addr, Variant::OP_EQUAL, p_type_addr, type_stringname_addr); + codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_OR, type_equality_addr, tmp_comp_addr); - 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->pop_temporary(); // Remove tmp_comp_addr from stack. + } else if (literal_type == Variant::STRING_NAME) { + GDScriptCodeGenerator::Address type_string_addr = codegen.add_constant(Variant::STRING); - 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); + // Check String <-> StringName type equality. + GDScriptCodeGenerator::Address tmp_comp_addr = codegen.add_temporary(equality_type); - 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_binary_operator(tmp_comp_addr, Variant::OP_EQUAL, p_type_addr, type_string_addr); + codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_OR, type_equality_addr, tmp_comp_addr); + + codegen.generator->pop_temporary(); // Remove tmp_comp_addr from stack. + } codegen.generator->write_and_left_operand(type_equality_addr); @@ -1349,9 +1422,22 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c } else if (!p_is_first) { codegen.generator->write_or_left_operand(p_previous_test); } + + GDScriptCodeGenerator::Address type_string_addr = codegen.add_constant(Variant::STRING); + GDScriptCodeGenerator::Address type_stringname_addr = codegen.add_constant(Variant::STRING_NAME); + + // Equality is always a boolean. + GDScriptDataType equality_type; + equality_type.has_type = true; + equality_type.kind = GDScriptDataType::BUILTIN; + equality_type.builtin_type = Variant::BOOL; + // Create the result temps first since it's the last to go away. - GDScriptCodeGenerator::Address result_addr = codegen.add_temporary(); - GDScriptCodeGenerator::Address equality_test_addr = codegen.add_temporary(); + GDScriptCodeGenerator::Address result_addr = codegen.add_temporary(equality_type); + GDScriptCodeGenerator::Address equality_test_addr = codegen.add_temporary(equality_type); + GDScriptCodeGenerator::Address stringy_comp_addr = codegen.add_temporary(equality_type); + GDScriptCodeGenerator::Address stringy_comp_addr_2 = codegen.add_temporary(equality_type); + GDScriptCodeGenerator::Address expr_type_addr = codegen.add_temporary(); // Evaluate expression. GDScriptCodeGenerator::Address expr_addr; @@ -1363,10 +1449,27 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c // Evaluate expression type. Vector<GDScriptCodeGenerator::Address> typeof_args; typeof_args.push_back(expr_addr); - codegen.generator->write_call_utility(result_addr, "typeof", typeof_args); + codegen.generator->write_call_utility(expr_type_addr, "typeof", typeof_args); // Check type equality. - codegen.generator->write_binary_operator(result_addr, Variant::OP_EQUAL, p_type_addr, result_addr); + codegen.generator->write_binary_operator(result_addr, Variant::OP_EQUAL, p_type_addr, expr_type_addr); + + // Check for String <-> StringName comparison. + codegen.generator->write_binary_operator(stringy_comp_addr, Variant::OP_EQUAL, p_type_addr, type_string_addr); + codegen.generator->write_binary_operator(stringy_comp_addr_2, Variant::OP_EQUAL, expr_type_addr, type_stringname_addr); + codegen.generator->write_binary_operator(stringy_comp_addr, Variant::OP_AND, stringy_comp_addr, stringy_comp_addr_2); + codegen.generator->write_binary_operator(result_addr, Variant::OP_OR, result_addr, stringy_comp_addr); + + // Check for StringName <-> String comparison. + codegen.generator->write_binary_operator(stringy_comp_addr, Variant::OP_EQUAL, p_type_addr, type_stringname_addr); + codegen.generator->write_binary_operator(stringy_comp_addr_2, Variant::OP_EQUAL, expr_type_addr, type_string_addr); + codegen.generator->write_binary_operator(stringy_comp_addr, Variant::OP_AND, stringy_comp_addr, stringy_comp_addr_2); + codegen.generator->write_binary_operator(result_addr, Variant::OP_OR, result_addr, stringy_comp_addr); + + codegen.generator->pop_temporary(); // Remove expr_type_addr from stack. + codegen.generator->pop_temporary(); // Remove stringy_comp_addr_2 from stack. + codegen.generator->pop_temporary(); // Remove stringy_comp_addr from stack. + codegen.generator->write_and_left_operand(result_addr); // Check value equality. @@ -1380,7 +1483,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c if (expr_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } - codegen.generator->pop_temporary(); // Remove type equality temporary. + codegen.generator->pop_temporary(); // Remove equality_test_addr from stack. // If this isn't the first, we need to OR with the previous pattern. If it's nested, we use AND instead. if (p_is_nested) { @@ -2062,11 +2165,11 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ } GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script); - GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type); - if (field_type.has_type) { codegen.generator->write_newline(field->start_line); + GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type); + if (field_type.has_container_element_type()) { codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); } else if (field_type.kind == GDScriptDataType::BUILTIN) { @@ -2093,9 +2196,6 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ continue; } - GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script); - - GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type); if (field->initializer) { // Emit proper line change. codegen.generator->write_newline(field->initializer->start_line); @@ -2106,6 +2206,9 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ return nullptr; } + GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script); + GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type); + if (field->use_conversion_assign) { codegen.generator->write_assign_with_conversion(dst_address, src_address); } else { @@ -2235,6 +2338,8 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS codegen.is_static = is_static; codegen.generator->write_start(p_script, func_name, is_static, rpc_config, return_type); + // The static initializer is always called on the same class where the static variables are defined, + // so the CLASS address (current class) can be used instead of `codegen.add_constant(p_script)`. GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS); // Initialize the default values for typed variables before anything. @@ -2251,20 +2356,18 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS } GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script); - if (field_type.has_type) { codegen.generator->write_newline(field->start_line); if (field_type.has_container_element_type()) { GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type); codegen.generator->write_construct_typed_array(temp, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); - codegen.generator->write_set_named(class_addr, field->identifier->name, temp); + codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index); codegen.generator->pop_temporary(); - } else if (field_type.kind == GDScriptDataType::BUILTIN) { GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type); codegen.generator->write_construct(temp, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); - codegen.generator->write_set_named(class_addr, field->identifier->name, temp); + codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index); codegen.generator->pop_temporary(); } // The `else` branch is for objects, in such case we leave it as `null`. @@ -2281,8 +2384,6 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS continue; } - GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script); - if (field->initializer) { // Emit proper line change. codegen.generator->write_newline(field->initializer->start_line); @@ -2293,7 +2394,9 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS return nullptr; } + GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script); GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type); + if (field->use_conversion_assign) { codegen.generator->write_assign_with_conversion(temp, src_address); } else { @@ -2303,7 +2406,7 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS codegen.generator->pop_temporary(); } - codegen.generator->write_set_named(class_addr, field->identifier->name, temp); + codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index); codegen.generator->pop_temporary(); } } diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 2d15d461fb..494eef41d9 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -31,12 +31,13 @@ #ifndef GDSCRIPT_COMPILER_H #define GDSCRIPT_COMPILER_H -#include "core/templates/hash_set.h" #include "gdscript.h" #include "gdscript_codegen.h" #include "gdscript_function.h" #include "gdscript_parser.h" +#include "core/templates/hash_set.h" + class GDScriptCompiler { const GDScriptParser *parser = nullptr; HashSet<GDScript *> parsed_classes; diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index 45ad8792d9..0438bd8903 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -30,10 +30,10 @@ #ifdef DEBUG_ENABLED +#include "gdscript.h" #include "gdscript_function.h" #include "core/string/string_builder.h" -#include "gdscript.h" static String _get_variant_string(const Variant &p_variant) { String txt; @@ -312,6 +312,36 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 3; } break; + case OPCODE_SET_STATIC_VARIABLE: { + text += "set_static_variable script("; + Ref<GDScript> gdscript = get_constant(_code_ptr[ip + 2] & GDScriptFunction::ADDR_MASK); + text += gdscript.is_valid() ? gdscript->get_fully_qualified_name().get_file() : "<unknown script>"; + text += ")"; + if (gdscript.is_valid()) { + text += "[\"" + gdscript->debug_get_static_var_by_index(_code_ptr[ip + 3]) + "\"]"; + } else { + text += "[<index " + itos(_code_ptr[ip + 3]) + ">]"; + } + text += " = "; + text += DADDR(1); + + incr += 4; + } break; + case OPCODE_GET_STATIC_VARIABLE: { + text += "get_static_variable "; + text += DADDR(1); + text += " = script("; + Ref<GDScript> gdscript = get_constant(_code_ptr[ip + 2] & GDScriptFunction::ADDR_MASK); + text += gdscript.is_valid() ? gdscript->get_fully_qualified_name().get_file() : "<unknown script>"; + text += ")"; + if (gdscript.is_valid()) { + text += "[\"" + gdscript->debug_get_static_var_by_index(_code_ptr[ip + 3]) + "\"]"; + } else { + text += "[<index " + itos(_code_ptr[ip + 3]) + ">]"; + } + + incr += 4; + } break; case OPCODE_ASSIGN: { text += "assign "; text += DADDR(1); @@ -1130,4 +1160,4 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { } } -#endif +#endif // DEBUG_ENABLED diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 829567d734..cd34feb8b3 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -30,9 +30,6 @@ #include "gdscript.h" -#include "core/config/engine.h" -#include "core/core_constants.h" -#include "core/io/file_access.h" #include "gdscript_analyzer.h" #include "gdscript_compiler.h" #include "gdscript_parser.h" @@ -40,10 +37,17 @@ #include "gdscript_utility_functions.h" #ifdef TOOLS_ENABLED +#include "editor/script_templates/templates.gen.h" +#endif + +#include "core/config/engine.h" +#include "core/core_constants.h" +#include "core/io/file_access.h" + +#ifdef TOOLS_ENABLED #include "core/config/project_settings.h" #include "editor/editor_file_system.h" #include "editor/editor_settings.h" -#include "editor/script_templates/templates.gen.h" #endif void GDScriptLanguage::get_comment_delimiters(List<String> *p_delimiters) const { @@ -906,19 +910,20 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio } } -static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result) { +static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth = 0) { for (int i = 0; i < p_suite->locals.size(); i++) { ScriptLanguage::CodeCompletionOption option; + int location = p_recursion_depth == 0 ? ScriptLanguage::LOCATION_LOCAL : (p_recursion_depth | ScriptLanguage::LOCATION_PARENT_MASK); if (p_suite->locals[i].type == GDScriptParser::SuiteNode::Local::CONSTANT) { - option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, ScriptLanguage::LOCATION_LOCAL); + option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); option.default_value = p_suite->locals[i].constant->initializer->reduced_value; } else { - option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE, ScriptLanguage::LOCATION_LOCAL); + option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE, location); } r_result.insert(option.display, option); } if (p_suite->parent_block) { - _find_identifiers_in_suite(p_suite->parent_block, r_result); + _find_identifiers_in_suite(p_suite->parent_block, r_result, p_recursion_depth + 1); } } @@ -933,7 +938,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, int classes_processed = 0; while (clss) { for (int i = 0; i < clss->members.size(); i++) { - const int location = (classes_processed + p_recursion_depth) | ScriptLanguage::LOCATION_PARENT_MASK; + const int location = p_recursion_depth == 0 ? classes_processed : (p_recursion_depth | ScriptLanguage::LOCATION_PARENT_MASK); const GDScriptParser::ClassNode::Member &member = clss->members[i]; ScriptLanguage::CodeCompletionOption option; switch (member.type) { @@ -1025,7 +1030,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base while (!base_type.has_no_type()) { switch (base_type.kind) { case GDScriptParser::DataType::CLASS: { - _find_identifiers_in_class(base_type.class_type, p_only_functions, base_type.is_meta_type, false, r_result, p_recursion_depth + 1); + _find_identifiers_in_class(base_type.class_type, p_only_functions, base_type.is_meta_type, false, r_result, p_recursion_depth); // This already finds all parent identifiers, so we are done. base_type = GDScriptParser::DataType(); } break; @@ -1205,7 +1210,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context } if (p_context.current_class) { - _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth + 1); + _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth); } List<StringName> functions; @@ -3398,7 +3403,8 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } case GDScriptParser::COMPLETION_ASSIGN: case GDScriptParser::COMPLETION_CALL_ARGUMENTS: - case GDScriptParser::COMPLETION_IDENTIFIER: { + case GDScriptParser::COMPLETION_IDENTIFIER: + case GDScriptParser::COMPLETION_PROPERTY_METHOD: { GDScriptParser::DataType base_type; if (context.current_class) { if (context.type != GDScriptParser::COMPLETION_SUPER_METHOD) { @@ -3527,6 +3533,33 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co return OK; } } break; + case GDScriptParser::COMPLETION_TYPE_ATTRIBUTE: { + if (context.node == nullptr || context.node->type != GDScriptParser::Node::TYPE) { + break; + } + const GDScriptParser::TypeNode *type = static_cast<const GDScriptParser::TypeNode *>(context.node); + + GDScriptParser::DataType base_type; + const GDScriptParser::IdentifierNode *prev = nullptr; + for (const GDScriptParser::IdentifierNode *E : type->type_chain) { + if (E->name == p_symbol && prev != nullptr) { + base_type = prev->get_datatype(); + break; + } + prev = E; + } + if (base_type.kind != GDScriptParser::DataType::CLASS) { + GDScriptCompletionIdentifier base; + if (!_guess_expression_type(context, prev, base)) { + break; + } + base_type = base.type; + } + + if (_lookup_symbol_from_base(base_type, p_symbol, is_function, r_result) == OK) { + return OK; + } + } break; case GDScriptParser::COMPLETION_OVERRIDE_METHOD: { GDScriptParser::DataType base_type = context.current_class->base_type; @@ -3534,6 +3567,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co return OK; } } break; + case GDScriptParser::COMPLETION_PROPERTY_DECLARATION_OR_TYPE: case GDScriptParser::COMPLETION_TYPE_NAME_OR_VOID: case GDScriptParser::COMPLETION_TYPE_NAME: { GDScriptParser::DataType base_type = context.current_class->get_datatype(); diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 390e562e6f..9bbfb14f31 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -31,6 +31,8 @@ #ifndef GDSCRIPT_FUNCTION_H #define GDSCRIPT_FUNCTION_H +#include "gdscript_utility_functions.h" + #include "core/object/ref_counted.h" #include "core/object/script_language.h" #include "core/os/thread.h" @@ -38,7 +40,6 @@ #include "core/templates/pair.h" #include "core/templates/self_list.h" #include "core/variant/variant.h" -#include "gdscript_utility_functions.h" class GDScriptInstance; class GDScript; @@ -148,6 +149,7 @@ public: operator PropertyInfo() const { PropertyInfo info; + info.usage = PROPERTY_USAGE_NONE; if (has_type) { switch (kind) { case UNINITIALIZED: @@ -237,6 +239,8 @@ public: OPCODE_GET_NAMED_VALIDATED, OPCODE_SET_MEMBER, OPCODE_GET_MEMBER, + OPCODE_SET_STATIC_VARIABLE, // Only for GDScript. + OPCODE_GET_STATIC_VARIABLE, // Only for GDScript. OPCODE_ASSIGN, OPCODE_ASSIGN_TRUE, OPCODE_ASSIGN_FALSE, @@ -409,14 +413,14 @@ public: ADDR_TYPE_STACK = 0, ADDR_TYPE_CONSTANT = 1, ADDR_TYPE_MEMBER = 2, - ADDR_TYPE_STATIC_VAR = 3, - ADDR_TYPE_MAX = 4, + ADDR_TYPE_MAX = 3, }; enum FixedAddresses { ADDR_STACK_SELF = 0, ADDR_STACK_CLASS = 1, ADDR_STACK_NIL = 2, + FIXED_ADDRESSES_MAX = 3, ADDR_SELF = ADDR_STACK_SELF | (ADDR_TYPE_STACK << ADDR_BITS), ADDR_CLASS = ADDR_STACK_CLASS | (ADDR_TYPE_STACK << ADDR_BITS), ADDR_NIL = ADDR_STACK_NIL | (ADDR_TYPE_STACK << ADDR_BITS), diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp index e9fe17bb17..9e14e43a58 100644 --- a/modules/gdscript/gdscript_lambda_callable.cpp +++ b/modules/gdscript/gdscript_lambda_callable.cpp @@ -30,9 +30,10 @@ #include "gdscript_lambda_callable.h" -#include "core/templates/hashfuncs.h" #include "gdscript.h" +#include "core/templates/hashfuncs.h" + bool GDScriptLambdaCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) { // Lambda callables are only compared by reference. return p_a == p_b; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index fdbd505975..cf750958ee 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -30,23 +30,27 @@ #include "gdscript_parser.h" +#include "gdscript.h" + +#ifdef DEBUG_ENABLED +#include "gdscript_warning.h" +#endif + #include "core/config/project_settings.h" #include "core/io/file_access.h" #include "core/io/resource_loader.h" #include "core/math/math_defs.h" -#include "gdscript.h" #include "scene/main/multiplayer_api.h" #ifdef DEBUG_ENABLED #include "core/os/os.h" #include "core/string/string_builder.h" -#include "gdscript_warning.h" #include "servers/text_server.h" -#endif // DEBUG_ENABLED +#endif #ifdef TOOLS_ENABLED #include "editor/editor_settings.h" -#endif // TOOLS_ENABLED +#endif static HashMap<StringName, Variant::Type> builtin_types; Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { @@ -112,7 +116,7 @@ GDScriptParser::GDScriptParser() { // Warning annotations. register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true); // Networking. - register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0), true); + register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("authority", "call_remote", "unreliable", 0)); #ifdef DEBUG_ENABLED is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable"); @@ -2760,12 +2764,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode switch (dictionary->style) { case DictionaryNode::LUA_TABLE: if (key != nullptr && key->type != Node::IDENTIFIER && key->type != Node::LITERAL) { - push_error("Expected identifier or string as LUA-style dictionary key."); + push_error(R"(Expected identifier or string as Lua-style dictionary key (e.g "{ key = value }").)"); advance(); break; } if (key != nullptr && key->type == Node::LITERAL && static_cast<LiteralNode *>(key)->value.get_type() != Variant::STRING) { - push_error("Expected identifier or string as LUA-style dictionary key."); + push_error(R"(Expected identifier or string as Lua-style dictionary key (e.g "{ key = value }").)"); advance(); break; } @@ -3293,31 +3297,114 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { } #ifdef TOOLS_ENABLED -static bool _in_codeblock(String p_line, bool p_already_in, int *r_block_begins = nullptr) { - int start_block = p_line.rfind("[codeblock]"); - int end_block = p_line.rfind("[/codeblock]"); - - if (start_block != -1 && r_block_begins) { - *r_block_begins = start_block; +enum DocLineState { + DOC_LINE_NORMAL, + DOC_LINE_IN_CODE, + DOC_LINE_IN_CODEBLOCK, +}; + +static String _process_doc_line(const String &p_line, const String &p_text, const String &p_space_prefix, DocLineState &r_state) { + String line = p_line; + if (r_state == DOC_LINE_NORMAL) { + line = line.strip_edges(true, false); + } else { + line = line.trim_prefix(p_space_prefix); } - if (p_already_in) { - if (end_block == -1) { - return true; - } else if (start_block == -1) { - return false; + String line_join; + if (!p_text.is_empty()) { + if (r_state == DOC_LINE_NORMAL) { + if (p_text.ends_with("[/codeblock]")) { + line_join = "\n"; + } else if (!p_text.ends_with("[br]")) { + line_join = " "; + } } else { - return start_block > end_block; + line_join = "\n"; } - } else { - if (start_block == -1) { - return false; - } else if (end_block == -1) { - return true; - } else { - return start_block > end_block; + } + + String result; + int from = 0; + int buffer_start = 0; + const int len = line.length(); + bool process = true; + while (process) { + switch (r_state) { + case DOC_LINE_NORMAL: { + int lb_pos = line.find_char('[', from); + if (lb_pos < 0) { + process = false; + break; + } + int rb_pos = line.find_char(']', lb_pos + 1); + if (rb_pos < 0) { + process = false; + break; + } + + from = rb_pos + 1; + + String tag = line.substr(lb_pos + 1, rb_pos - lb_pos - 1); + if (tag == "code") { + r_state = DOC_LINE_IN_CODE; + } else if (tag == "codeblock") { + if (lb_pos == 0) { + line_join = "\n"; + } else { + result += line.substr(buffer_start, lb_pos - buffer_start) + '\n'; + } + result += "[codeblock]"; + if (from < len) { + result += '\n'; + } + + r_state = DOC_LINE_IN_CODEBLOCK; + buffer_start = from; + } + } break; + case DOC_LINE_IN_CODE: { + int pos = line.find("[/code]", from); + if (pos < 0) { + process = false; + break; + } + + from = pos + 7; + + r_state = DOC_LINE_NORMAL; + } break; + case DOC_LINE_IN_CODEBLOCK: { + int pos = line.find("[/codeblock]", from); + if (pos < 0) { + process = false; + break; + } + + from = pos + 12; + + if (pos == 0) { + line_join = "\n"; + } else { + result += line.substr(buffer_start, pos - buffer_start) + '\n'; + } + result += "[/codeblock]"; + if (from < len) { + result += '\n'; + } + + r_state = DOC_LINE_NORMAL; + buffer_start = from; + } break; } } + + result += line.substr(buffer_start); + if (r_state == DOC_LINE_NORMAL) { + result = result.strip_edges(false, true); + } + + return line_join + result; } bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) { @@ -3345,7 +3432,7 @@ String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) { String doc; int line = p_line; - bool in_codeblock = false; + DocLineState state = DOC_LINE_NORMAL; while (comments.has(line - 1)) { if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) { @@ -3354,29 +3441,24 @@ String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) { line--; } - int codeblock_begins = 0; + String space_prefix; + if (comments.has(line) && comments[line].comment.begins_with("##")) { + int i = 2; + for (; i < comments[line].comment.length(); i++) { + if (comments[line].comment[i] != ' ') { + break; + } + } + space_prefix = String(" ").repeat(i - 2); + } + while (comments.has(line)) { if (!comments[line].new_line || !comments[line].comment.begins_with("##")) { break; } - String doc_line = comments[line].comment.trim_prefix("##"); - in_codeblock = _in_codeblock(doc_line, in_codeblock, &codeblock_begins); - - if (in_codeblock) { - int i = 0; - for (; i < codeblock_begins; i++) { - if (doc_line[i] != ' ') { - break; - } - } - doc_line = doc_line.substr(i); - } else { - doc_line = doc_line.strip_edges(); - } - String line_join = (in_codeblock) ? "\n" : " "; - - doc = (doc.is_empty()) ? doc_line : doc + line_join + doc_line; + String doc_line = comments[line].comment.trim_prefix("##"); + doc += _process_doc_line(doc_line, doc, space_prefix, state); line++; } @@ -3391,7 +3473,7 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & ERR_FAIL_COND(!p_brief.is_empty() || !p_desc.is_empty() || p_tutorials.size() != 0); int line = p_line; - bool in_codeblock = false; + DocLineState state = DOC_LINE_NORMAL; enum Mode { BRIEF, DESC, @@ -3409,96 +3491,87 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & } } - int codeblock_begins = 0; + String space_prefix; + if (comments.has(line) && comments[line].comment.begins_with("##")) { + int i = 2; + for (; i < comments[line].comment.length(); i++) { + if (comments[line].comment[i] != ' ') { + break; + } + } + space_prefix = String(" ").repeat(i - 2); + } + while (comments.has(line)) { if (!comments[line].new_line || !comments[line].comment.begins_with("##")) { break; } - String title, link; // For tutorials. String doc_line = comments[line++].comment.trim_prefix("##"); - String stripped_line = doc_line.strip_edges(); - - // Set the read mode. - if (stripped_line.is_empty() && mode == BRIEF && !p_brief.is_empty()) { - mode = DESC; - continue; - - } else if (stripped_line.begins_with("@tutorial")) { - int begin_scan = String("@tutorial").length(); - if (begin_scan >= stripped_line.length()) { - continue; // invalid syntax. - } - - if (stripped_line[begin_scan] == ':') { // No title. - // Syntax: ## @tutorial: https://godotengine.org/ // The title argument is optional. - title = ""; - link = stripped_line.trim_prefix("@tutorial:").strip_edges(); - - } else { - /* Syntax: - * @tutorial ( The Title Here ) : https://the.url/ - * ^ open ^ close ^ colon ^ url - */ - int open_bracket_pos = begin_scan, close_bracket_pos = 0; - while (open_bracket_pos < stripped_line.length() && (stripped_line[open_bracket_pos] == ' ' || stripped_line[open_bracket_pos] == '\t')) { - open_bracket_pos++; - } - if (open_bracket_pos == stripped_line.length() || stripped_line[open_bracket_pos++] != '(') { - continue; // invalid syntax. - } - close_bracket_pos = open_bracket_pos; - while (close_bracket_pos < stripped_line.length() && stripped_line[close_bracket_pos] != ')') { - close_bracket_pos++; - } - if (close_bracket_pos == stripped_line.length()) { - continue; // invalid syntax. - } + String title, link; // For tutorials. - int colon_pos = close_bracket_pos + 1; - while (colon_pos < stripped_line.length() && (stripped_line[colon_pos] == ' ' || stripped_line[colon_pos] == '\t')) { - colon_pos++; + if (state == DOC_LINE_NORMAL) { + // Set the read mode. + String stripped_line = doc_line.strip_edges(); + if (stripped_line.is_empty()) { + if (mode == BRIEF && !p_brief.is_empty()) { + mode = DESC; } - if (colon_pos == stripped_line.length() || stripped_line[colon_pos++] != ':') { - continue; // invalid syntax. + continue; + } else if (stripped_line.begins_with("@tutorial")) { + int begin_scan = String("@tutorial").length(); + if (begin_scan >= stripped_line.length()) { + continue; // Invalid syntax. } - title = stripped_line.substr(open_bracket_pos, close_bracket_pos - open_bracket_pos).strip_edges(); - link = stripped_line.substr(colon_pos).strip_edges(); - } - - mode = TUTORIALS; - in_codeblock = false; - } else if (stripped_line.is_empty()) { - continue; - } else { - // Tutorial docs are single line, we need a @tag after it. - if (mode == TUTORIALS) { - mode = DONE; - } + if (stripped_line[begin_scan] == ':') { // No title. + // Syntax: ## @tutorial: https://godotengine.org/ // The title argument is optional. + title = ""; + link = stripped_line.trim_prefix("@tutorial:").strip_edges(); + } else { + /* Syntax: + * @tutorial ( The Title Here ) : https://the.url/ + * ^ open ^ close ^ colon ^ url + */ + int open_bracket_pos = begin_scan, close_bracket_pos = 0; + while (open_bracket_pos < stripped_line.length() && (stripped_line[open_bracket_pos] == ' ' || stripped_line[open_bracket_pos] == '\t')) { + open_bracket_pos++; + } + if (open_bracket_pos == stripped_line.length() || stripped_line[open_bracket_pos++] != '(') { + continue; // Invalid syntax. + } + close_bracket_pos = open_bracket_pos; + while (close_bracket_pos < stripped_line.length() && stripped_line[close_bracket_pos] != ')') { + close_bracket_pos++; + } + if (close_bracket_pos == stripped_line.length()) { + continue; // Invalid syntax. + } - in_codeblock = _in_codeblock(doc_line, in_codeblock, &codeblock_begins); - } + int colon_pos = close_bracket_pos + 1; + while (colon_pos < stripped_line.length() && (stripped_line[colon_pos] == ' ' || stripped_line[colon_pos] == '\t')) { + colon_pos++; + } + if (colon_pos == stripped_line.length() || stripped_line[colon_pos++] != ':') { + continue; // Invalid syntax. + } - if (in_codeblock) { - int i = 0; - for (; i < codeblock_begins; i++) { - if (doc_line[i] != ' ') { - break; + title = stripped_line.substr(open_bracket_pos, close_bracket_pos - open_bracket_pos).strip_edges(); + link = stripped_line.substr(colon_pos).strip_edges(); } + + mode = TUTORIALS; + } else if (mode == TUTORIALS) { // Tutorial docs are single line, we need a @tag after it. + mode = DONE; } - doc_line = doc_line.substr(i); - } else { - doc_line = stripped_line; } - String line_join = (in_codeblock) ? "\n" : " "; switch (mode) { case BRIEF: - p_brief = (p_brief.length() == 0) ? doc_line : p_brief + line_join + doc_line; + p_brief += _process_doc_line(doc_line, p_brief, space_prefix, state); break; case DESC: - p_desc = (p_desc.length() == 0) ? doc_line : p_desc + line_join + doc_line; + p_desc += _process_doc_line(doc_line, p_desc, space_prefix, state); break; case TUTORIALS: p_tutorials.append(Pair<String, String>(title, link)); @@ -3507,6 +3580,7 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & break; } } + if (current_class->members.size() > 0) { const ClassNode::Member &m = current_class->members[0]; int first_member_line = m.get_line(); @@ -3753,8 +3827,12 @@ bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node } VariableNode *variable = static_cast<VariableNode *>(p_node); + if (variable->is_static) { + push_error(R"("@onready" annotation cannot be applied to a static variable.)", p_annotation); + return false; + } if (variable->onready) { - push_error(R"("@onready" annotation can only be used once per variable.)"); + push_error(R"("@onready" annotation can only be used once per variable.)", p_annotation); return false; } variable->onready = true; @@ -3767,6 +3845,10 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); VariableNode *variable = static_cast<VariableNode *>(p_node); + if (variable->is_static) { + push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation); + return false; + } if (variable->exported) { push_error(vformat(R"(Annotation "%s" cannot be used with another "@export" annotation.)", p_annotation->name), p_annotation); return false; @@ -4067,21 +4149,16 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_ Dictionary rpc_config; rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY; if (!p_annotation->resolved_arguments.is_empty()) { - int last = p_annotation->resolved_arguments.size() - 1; - if (p_annotation->resolved_arguments[last].get_type() == Variant::INT) { - rpc_config["channel"] = p_annotation->resolved_arguments[last].operator int(); - last -= 1; - } - if (last > 3) { - push_error(R"(Invalid RPC arguments. At most 4 arguments are allowed, where only the last argument can be an integer to specify the channel.')", p_annotation); - return false; - } - unsigned char locality_args = 0; unsigned char permission_args = 0; unsigned char transfer_mode_args = 0; - for (int i = last; i >= 0; i--) { + for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) { + if (i == 3) { + rpc_config["channel"] = p_annotation->resolved_arguments[i].operator int(); + continue; + } + String arg = p_annotation->resolved_arguments[i].operator String(); if (arg == "call_local") { locality_args++; diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 8f0265510f..ad08c3bfd6 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -31,6 +31,13 @@ #ifndef GDSCRIPT_PARSER_H #define GDSCRIPT_PARSER_H +#include "gdscript_cache.h" +#include "gdscript_tokenizer.h" + +#ifdef DEBUG_ENABLED +#include "gdscript_warning.h" +#endif + #include "core/io/resource.h" #include "core/object/ref_counted.h" #include "core/object/script_language.h" @@ -41,13 +48,10 @@ #include "core/templates/rb_map.h" #include "core/templates/vector.h" #include "core/variant/variant.h" -#include "gdscript_cache.h" -#include "gdscript_tokenizer.h" #ifdef DEBUG_ENABLED #include "core/string/string_builder.h" -#include "gdscript_warning.h" -#endif // DEBUG_ENABLED +#endif class GDScriptParser { struct AnnotationInfo; diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index a45a73a8d5..3927a4dd3e 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -33,13 +33,14 @@ #include "core/error/error_macros.h" #include "core/string/char_utils.h" -#ifdef TOOLS_ENABLED -#include "editor/editor_settings.h" -#endif #ifdef DEBUG_ENABLED #include "servers/text_server.h" #endif +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif + static const char *token_names[] = { "Empty", // EMPTY, // Basic @@ -162,6 +163,24 @@ const char *GDScriptTokenizer::Token::get_name() const { return token_names[type]; } +bool GDScriptTokenizer::Token::can_precede_bin_op() const { + switch (type) { + case IDENTIFIER: + case LITERAL: + case SELF: + case BRACKET_CLOSE: + case BRACE_CLOSE: + case PARENTHESIS_CLOSE: + case CONST_PI: + case CONST_TAU: + case CONST_INF: + case CONST_NAN: + return true; + default: + return false; + } +} + bool GDScriptTokenizer::Token::is_identifier() const { // Note: Most keywords should not be recognized as identifiers. // These are only exceptions for stuff that already is on the engine's API. @@ -382,6 +401,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) { } } + last_token = token; return token; } @@ -627,6 +647,7 @@ void GDScriptTokenizer::newline(bool p_make_token) { newline.leftmost_column = newline.start_column; newline.rightmost_column = newline.end_column; pending_newline = true; + last_token = newline; last_newline = newline; } @@ -643,6 +664,11 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { bool has_error = false; bool (*digit_check_func)(char32_t) = is_digit; + // Sign before hexadecimal or binary. + if ((_peek(-1) == '+' || _peek(-1) == '-') && _peek() == '0') { + _advance(); + } + if (_peek(-1) == '.') { has_decimal = true; } else if (_peek(-1) == '0') { @@ -659,12 +685,20 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { } } - // Allow '_' to be used in a number, for readability. - bool previous_was_underscore = false; + if (base != 10 && is_underscore(_peek())) { // Disallow `0x_` and `0b_`. + Token error = make_error(vformat(R"(Unexpected underscore after "0%c".)", _peek(-1))); + error.start_column = column; + error.leftmost_column = column; + error.end_column = column + 1; + error.rightmost_column = column + 1; + push_error(error); + has_error = true; + } + bool previous_was_underscore = false; // Allow `_` to be used in a number, for readability. while (digit_check_func(_peek()) || is_underscore(_peek())) { if (is_underscore(_peek())) { if (previous_was_underscore) { - Token error = make_error(R"(Only one underscore can be used as a numeric separator.)"); + Token error = make_error(R"(Multiple underscores cannot be adjacent in a numeric literal.)"); error.start_column = column; error.leftmost_column = column; error.end_column = column + 1; @@ -711,7 +745,30 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { _advance(); // Consume decimal digits. + if (is_underscore(_peek())) { // Disallow `10._`, but allow `10.`. + Token error = make_error(R"(Unexpected underscore after decimal point.)"); + error.start_column = column; + error.leftmost_column = column; + error.end_column = column + 1; + error.rightmost_column = column + 1; + push_error(error); + has_error = true; + } + previous_was_underscore = false; while (is_digit(_peek()) || is_underscore(_peek())) { + if (is_underscore(_peek())) { + if (previous_was_underscore) { + Token error = make_error(R"(Multiple underscores cannot be adjacent in a numeric literal.)"); + error.start_column = column; + error.leftmost_column = column; + error.end_column = column + 1; + error.rightmost_column = column + 1; + push_error(error); + } + previous_was_underscore = true; + } else { + previous_was_underscore = false; + } _advance(); } } @@ -737,7 +794,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { while (is_digit(_peek()) || is_underscore(_peek())) { if (is_underscore(_peek())) { if (previous_was_underscore) { - Token error = make_error(R"(Only one underscore can be used as a numeric separator.)"); + Token error = make_error(R"(Multiple underscores cannot be adjacent in a numeric literal.)"); error.start_column = column; error.leftmost_column = column; error.end_column = column + 1; @@ -1431,6 +1488,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { if (_peek() == '=') { _advance(); return make_token(Token::PLUS_EQUAL); + } else if (is_digit(_peek()) && !last_token.can_precede_bin_op()) { + // Number starting with '+'. + return number(); } else { return make_token(Token::PLUS); } @@ -1438,6 +1498,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { if (_peek() == '=') { _advance(); return make_token(Token::MINUS_EQUAL); + } else if (is_digit(_peek()) && !last_token.can_precede_bin_op()) { + // Number starting with '-'. + return number(); } else if (_peek() == '>') { _advance(); return make_token(Token::FORWARD_ARROW); @@ -1547,9 +1610,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { default: if (is_whitespace(c)) { - return make_error(vformat(R"(Invalid white space character "\\u%X".)", static_cast<int32_t>(c))); + return make_error(vformat(R"(Invalid white space character U+%04X.)", static_cast<int32_t>(c))); } else { - return make_error(vformat(R"(Unknown character "%s".)", String(&c, 1))); + return make_error(vformat(R"(Invalid character "%c" (U+%04X).)", c, static_cast<int32_t>(c))); } } } diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index 608840d3f1..068393cee9 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -171,6 +171,7 @@ public: String source; const char *get_name() const; + bool can_precede_bin_op() const; bool is_identifier() const; bool is_node_name() const; StringName get_identifier() const { return source; } @@ -216,6 +217,7 @@ private: bool multiline_mode = false; List<Token> error_stack; bool pending_newline = false; + Token last_token; Token last_newline; int pending_indents = 0; List<int> indent_stack; diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index 8862450121..030950267d 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -30,6 +30,8 @@ #include "gdscript_utility_functions.h" +#include "gdscript.h" + #include "core/io/resource_loader.h" #include "core/object/class_db.h" #include "core/object/method_bind.h" @@ -37,7 +39,6 @@ #include "core/templates/oa_hash_map.h" #include "core/templates/vector.h" #include "core/variant/typed_array.h" -#include "gdscript.h" #ifdef DEBUG_ENABLED diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index bebf34cbb3..4545689bd9 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -28,12 +28,12 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ +#include "gdscript.h" #include "gdscript_function.h" +#include "gdscript_lambda_callable.h" #include "core/core_string_names.h" #include "core/os/os.h" -#include "gdscript.h" -#include "gdscript_lambda_callable.h" #ifdef DEBUG_ENABLED static String _get_script_name(const Ref<Script> p_script) { @@ -217,6 +217,8 @@ void (*type_init_function_table[])(Variant *) = { &&OPCODE_GET_NAMED_VALIDATED, \ &&OPCODE_SET_MEMBER, \ &&OPCODE_GET_MEMBER, \ + &&OPCODE_SET_STATIC_VARIABLE, \ + &&OPCODE_GET_STATIC_VARIABLE, \ &&OPCODE_ASSIGN, \ &&OPCODE_ASSIGN_TRUE, \ &&OPCODE_ASSIGN_FALSE, \ @@ -666,7 +668,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a Variant *m_v = instruction_args[m_idx] #ifdef DEBUG_ENABLED - uint64_t function_start_time = 0; uint64_t function_call_time = 0; @@ -679,11 +680,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a bool exit_ok = false; bool awaited = false; #endif + #ifdef DEBUG_ENABLED - int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0, script->static_variables.size() }; + int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0 }; #endif - Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr, script->static_variables.ptrw() }; + Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr }; #ifdef DEBUG_ENABLED OPCODE_WHILE(ip < _code_size) { @@ -1171,6 +1173,42 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_SET_STATIC_VARIABLE) { + CHECK_SPACE(4); + + GET_VARIANT_PTR(value, 0); + + GET_VARIANT_PTR(_class, 1); + GDScript *gdscript = Object::cast_to<GDScript>(_class->operator Object *()); + GD_ERR_BREAK(!gdscript); + + int index = _code_ptr[ip + 3]; + GD_ERR_BREAK(index < 0 || index >= gdscript->static_variables.size()); + + gdscript->static_variables.write[index] = *value; + + ip += 4; + } + DISPATCH_OPCODE; + + OPCODE(OPCODE_GET_STATIC_VARIABLE) { + CHECK_SPACE(4); + + GET_VARIANT_PTR(target, 0); + + GET_VARIANT_PTR(_class, 1); + GDScript *gdscript = Object::cast_to<GDScript>(_class->operator Object *()); + GD_ERR_BREAK(!gdscript); + + int index = _code_ptr[ip + 3]; + GD_ERR_BREAK(index < 0 || index >= gdscript->static_variables.size()); + + *target = gdscript->static_variables[index]; + + ip += 4; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_ASSIGN) { CHECK_SPACE(3); GET_VARIANT_PTR(dst, 0); @@ -3620,7 +3658,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #endif // Free stack, except reserved addresses. - for (int i = 3; i < _stack_size; i++) { + for (int i = FIXED_ADDRESSES_MAX; i < _stack_size; i++) { stack[i].~Variant(); } #ifdef DEBUG_ENABLED @@ -3628,7 +3666,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #endif // Always free reserved addresses, since they are never copied. - for (int i = 0; i < 3; i++) { + for (int i = 0; i < FIXED_ADDRESSES_MAX; i++) { stack[i].~Variant(); } diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index 4500cb01f3..4fd27de081 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -32,9 +32,10 @@ #define GDSCRIPT_EXTEND_PARSER_H #include "../gdscript_parser.h" -#include "core/variant/variant.h" #include "godot_lsp.h" +#include "core/variant/variant.h" + #ifndef LINE_NUMBER_TO_INDEX #define LINE_NUMBER_TO_INDEX(p_line) ((p_line)-1) #endif diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h index d25814f8aa..a4d9dc6b1d 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.h +++ b/modules/gdscript/language_server/gdscript_language_protocol.h @@ -31,13 +31,14 @@ #ifndef GDSCRIPT_LANGUAGE_PROTOCOL_H #define GDSCRIPT_LANGUAGE_PROTOCOL_H -#include "core/io/stream_peer.h" -#include "core/io/stream_peer_tcp.h" -#include "core/io/tcp_server.h" #include "gdscript_text_document.h" #include "gdscript_workspace.h" #include "godot_lsp.h" +#include "core/io/stream_peer.h" +#include "core/io/stream_peer_tcp.h" +#include "core/io/tcp_server.h" + #include "modules/modules_enabled.gen.h" // For jsonrpc. #ifdef MODULE_JSONRPC_ENABLED #include "modules/jsonrpc/jsonrpc.h" diff --git a/modules/gdscript/language_server/gdscript_language_server.h b/modules/gdscript/language_server/gdscript_language_server.h index a130dc3ac8..75f9403a74 100644 --- a/modules/gdscript/language_server/gdscript_language_server.h +++ b/modules/gdscript/language_server/gdscript_language_server.h @@ -32,9 +32,10 @@ #define GDSCRIPT_LANGUAGE_SERVER_H #include "../gdscript_parser.h" -#include "editor/editor_plugin.h" #include "gdscript_language_protocol.h" +#include "editor/editor_plugin.h" + class GDScriptLanguageServer : public EditorPlugin { GDCLASS(GDScriptLanguageServer, EditorPlugin); diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index 35fbdca949..92a5f55978 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -31,11 +31,12 @@ #include "gdscript_text_document.h" #include "../gdscript.h" +#include "gdscript_extend_parser.h" +#include "gdscript_language_protocol.h" + #include "core/os/os.h" #include "editor/editor_settings.h" #include "editor/plugins/script_text_editor.h" -#include "gdscript_extend_parser.h" -#include "gdscript_language_protocol.h" #include "servers/display_server.h" void GDScriptTextDocument::_bind_methods() { diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h index aeda10de89..0121101db2 100644 --- a/modules/gdscript/language_server/gdscript_text_document.h +++ b/modules/gdscript/language_server/gdscript_text_document.h @@ -31,9 +31,10 @@ #ifndef GDSCRIPT_TEXT_DOCUMENT_H #define GDSCRIPT_TEXT_DOCUMENT_H +#include "godot_lsp.h" + #include "core/io/file_access.h" #include "core/object/ref_counted.h" -#include "godot_lsp.h" class GDScriptTextDocument : public RefCounted { GDCLASS(GDScriptTextDocument, RefCounted) diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 7fc2962341..9f848b02f5 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -32,6 +32,8 @@ #include "../gdscript.h" #include "../gdscript_parser.h" +#include "gdscript_language_protocol.h" + #include "core/config/project_settings.h" #include "core/object/script_language.h" #include "editor/doc_tools.h" @@ -39,7 +41,6 @@ #include "editor/editor_help.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" -#include "gdscript_language_protocol.h" #include "scene/resources/packed_scene.h" void GDScriptWorkspace::_bind_methods() { diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h index a849ef8a8d..80653778fb 100644 --- a/modules/gdscript/language_server/gdscript_workspace.h +++ b/modules/gdscript/language_server/gdscript_workspace.h @@ -32,11 +32,12 @@ #define GDSCRIPT_WORKSPACE_H #include "../gdscript_parser.h" -#include "core/variant/variant.h" -#include "editor/editor_file_system.h" #include "gdscript_extend_parser.h" #include "godot_lsp.h" +#include "core/variant/variant.h" +#include "editor/editor_file_system.h" + class GDScriptWorkspace : public RefCounted { GDCLASS(GDScriptWorkspace, RefCounted); diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index b6feaadccf..e23bd50b8b 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -30,39 +30,51 @@ #include "register_types.h" -#include "core/io/dir_access.h" -#include "core/io/file_access.h" -#include "core/io/file_access_encrypted.h" -#include "core/io/resource_loader.h" #include "gdscript.h" #include "gdscript_analyzer.h" #include "gdscript_cache.h" #include "gdscript_tokenizer.h" #include "gdscript_utility_functions.h" +#ifdef TOOLS_ENABLED +#include "editor/gdscript_highlighter.h" +#include "editor/gdscript_translation_parser_plugin.h" + +#ifndef GDSCRIPT_NO_LSP +#include "language_server/gdscript_language_server.h" +#endif +#endif // TOOLS_ENABLED + #ifdef TESTS_ENABLED #include "tests/test_gdscript.h" -#include "tests/test_macros.h" #endif -GDScriptLanguage *script_language_gd = nullptr; -Ref<ResourceFormatLoaderGDScript> resource_loader_gd; -Ref<ResourceFormatSaverGDScript> resource_saver_gd; -GDScriptCache *gdscript_cache = nullptr; +#include "core/io/dir_access.h" +#include "core/io/file_access.h" +#include "core/io/file_access_encrypted.h" +#include "core/io/resource_loader.h" #ifdef TOOLS_ENABLED - #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "editor/editor_translation_parser.h" #include "editor/export/editor_export.h" -#include "editor/gdscript_highlighter.h" -#include "editor/gdscript_translation_parser_plugin.h" #ifndef GDSCRIPT_NO_LSP #include "core/config/engine.h" -#include "language_server/gdscript_language_server.h" -#endif // !GDSCRIPT_NO_LSP +#endif +#endif // TOOLS_ENABLED + +#ifdef TESTS_ENABLED +#include "tests/test_macros.h" +#endif + +GDScriptLanguage *script_language_gd = nullptr; +Ref<ResourceFormatLoaderGDScript> resource_loader_gd; +Ref<ResourceFormatSaverGDScript> resource_saver_gd; +GDScriptCache *gdscript_cache = nullptr; + +#ifdef TOOLS_ENABLED Ref<GDScriptEditorTranslationParserPlugin> gdscript_translation_parser_plugin; diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index b8448d16c2..874cbc6ee8 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -392,6 +392,9 @@ void GDScriptTest::error_handler(void *p_this, const char *p_function, const cha StringBuilder builder; builder.append(">> "); + // Only include the function, file and line for script errors, otherwise the + // test outputs changes based on the platform/compiler. + bool include_source_info = false; switch (p_type) { case ERR_HANDLER_ERROR: builder.append("ERROR"); @@ -401,6 +404,7 @@ void GDScriptTest::error_handler(void *p_this, const char *p_function, const cha break; case ERR_HANDLER_SCRIPT: builder.append("SCRIPT ERROR"); + include_source_info = true; break; case ERR_HANDLER_SHADER: builder.append("SHADER ERROR"); @@ -410,12 +414,14 @@ void GDScriptTest::error_handler(void *p_this, const char *p_function, const cha break; } - builder.append("\n>> on function: "); - builder.append(String::utf8(p_function)); - builder.append("()\n>> "); - builder.append(String::utf8(p_file).trim_prefix(self->base_dir)); - builder.append("\n>> "); - builder.append(itos(p_line)); + if (include_source_info) { + builder.append("\n>> on function: "); + builder.append(String::utf8(p_function)); + builder.append("()\n>> "); + builder.append(String::utf8(p_file).trim_prefix(self->base_dir).replace("\\", "/")); + builder.append("\n>> "); + builder.append(itos(p_line)); + } builder.append("\n>> "); builder.append(String::utf8(p_error)); if (strlen(p_explanation) > 0) { diff --git a/modules/gdscript/tests/gdscript_test_runner.h b/modules/gdscript/tests/gdscript_test_runner.h index 60b48c6a57..b1190604ad 100644 --- a/modules/gdscript/tests/gdscript_test_runner.h +++ b/modules/gdscript/tests/gdscript_test_runner.h @@ -32,6 +32,7 @@ #define GDSCRIPT_TEST_RUNNER_H #include "../gdscript.h" + #include "core/error/error_macros.h" #include "core/string/print_string.h" #include "core/string/ustring.h" diff --git a/modules/gdscript/tests/gdscript_test_runner_suite.h b/modules/gdscript/tests/gdscript_test_runner_suite.h index e27b6218f1..5fd7d942d2 100644 --- a/modules/gdscript/tests/gdscript_test_runner_suite.h +++ b/modules/gdscript/tests/gdscript_test_runner_suite.h @@ -32,6 +32,7 @@ #define GDSCRIPT_TEST_RUNNER_SUITE_H #include "gdscript_test_runner.h" + #include "tests/test_macros.h" namespace GDScriptTests { diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static.gd new file mode 100644 index 0000000000..1d8f076857 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static.gd @@ -0,0 +1,8 @@ +static func static_func(): + non_static_func() + +func non_static_func(): + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static.out b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static.out new file mode 100644 index 0000000000..b78f131345 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot call non-static function "non_static_func()" from static function "static_func()". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.gd new file mode 100644 index 0000000000..66697cbb29 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.gd @@ -0,0 +1,8 @@ +# GH-77098 p.3 + +@static_unload + +@export static var a: int + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.out b/modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.out new file mode 100644 index 0000000000..4111aa07af --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_export_annotation.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Annotation "@export" cannot be applied to a static variable. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/virtual_method_not_implemented.gd b/modules/gdscript/tests/scripts/analyzer/errors/virtual_method_not_implemented.gd new file mode 100644 index 0000000000..c34d927035 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/virtual_method_not_implemented.gd @@ -0,0 +1,2 @@ +func test(): + _get_property_list() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/virtual_method_not_implemented.out b/modules/gdscript/tests/scripts/analyzer/errors/virtual_method_not_implemented.out new file mode 100644 index 0000000000..ce2f49a5e5 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/virtual_method_not_implemented.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot call virtual function "_get_property_list()" because it hasn't been defined. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd new file mode 100644 index 0000000000..57dfffdbee --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd @@ -0,0 +1,5 @@ +func _init(): + super() + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out new file mode 100644 index 0000000000..e68759223c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot call the parent class' virtual function "_init()" because it hasn't been defined. diff --git a/modules/gdscript/tests/scripts/analyzer/features/boolean_operators_for_all_types.gd b/modules/gdscript/tests/scripts/analyzer/features/boolean_operators_for_all_types.gd new file mode 100644 index 0000000000..73d0f9096c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/boolean_operators_for_all_types.gd @@ -0,0 +1,347 @@ +extends Resource + +signal foo + +func test(): + var x + # TYPE_NIL + x = null + prints("TYPE_NIL") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_BOOL + x = true + prints("TYPE_BOOL") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_INT + x = 1 + prints("TYPE_INT") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_FLOAT + x = 1.1 + prints("TYPE_FLOAT") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_STRING + x = "foo" + prints("TYPE_STRING") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_VECTOR2 + x = Vector2(1, 1) + prints("TYPE_VECTOR2") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_VECTOR2I + x = Vector2i(1, 1) + prints("TYPE_VECTOR2I") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_RECT2 + x = Rect2(1, 1, 1, 1) + prints("TYPE_RECT2") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_RECT2I + x = Rect2i(1, 1, 1, 1) + prints("TYPE_RECT2I") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_VECTOR3 + x = Vector3(1, 1, 1) + prints("TYPE_VECTOR3") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_VECTOR3I + x = Vector3i(1, 1, 1) + prints("TYPE_VECTOR3I") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_TRANSFORM2D + x = Transform2D.IDENTITY + prints("TYPE_TRANSFORM2D") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_VECTOR4 + x = Vector4(1, 1, 1, 1) + prints("TYPE_VECTOR4") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_VECTOR4I + x = Vector4i(1, 1, 1, 1) + prints("TYPE_VECTOR4I") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_PLANE + x = Plane.PLANE_XY + prints("TYPE_PLANE") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_QUATERNION + x = Quaternion.IDENTITY + prints("TYPE_QUATERNION") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_AABB + x = AABB(Vector3.ONE, Vector3.ONE) + prints("TYPE_AABB") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_BASIS + x = Basis.IDENTITY + prints("TYPE_BASIS") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_TRANSFORM3D + x = Transform3D.IDENTITY + prints("TYPE_TRANSFORM3D") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_PROJECTION + x = Projection.IDENTITY + prints("TYPE_PROJECTION") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_COLOR + x = Color.WHITE + prints("TYPE_COLOR") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_STRING_NAME + x = &"name" + prints("TYPE_STRING_NAME") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_NODE_PATH + x = ^"path" + prints("TYPE_NODE_PATH") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_RID + x = get_rid() + prints("TYPE_RID") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_OBJECT + x = self + prints("TYPE_OBJECT") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_CALLABLE + x = test + prints("TYPE_CALLABLE") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_SIGNAL + x = foo + prints("TYPE_SIGNAL") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_DICTIONARY + x = { a = 1} + prints("TYPE_DICTIONARY") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_ARRAY + x = [1] + prints("TYPE_ARRAY") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_PACKED_BYTE_ARRAY + x = PackedByteArray([1]) + prints("TYPE_PACKED_BYTE_ARRAY") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_PACKED_INT32_ARRAY + x = PackedInt32Array([1]) + prints("TYPE_PACKED_INT32_ARRAY") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_PACKED_INT64_ARRAY + x = PackedInt64Array([1]) + prints("TYPE_PACKED_INT64_ARRAY") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_PACKED_FLOAT32_ARRAY + x = PackedFloat32Array([1]) + prints("TYPE_PACKED_FLOAT32_ARRAY") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_PACKED_FLOAT64_ARRAY + x = PackedFloat64Array([1]) + prints("TYPE_PACKED_FLOAT64_ARRAY") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_PACKED_STRING_ARRAY + x = PackedStringArray(["1"]) + prints("TYPE_PACKED_STRING_ARRAY") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_PACKED_VECTOR2_ARRAY + x = PackedVector2Array([Vector2.ONE]) + prints("TYPE_PACKED_VECTOR2_ARRAY") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_PACKED_VECTOR3_ARRAY + x = PackedVector3Array([Vector3.ONE]) + prints("TYPE_PACKED_VECTOR3_ARRAY") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) + + # TYPE_PACKED_COLOR_ARRAY + x = PackedColorArray([Color.WHITE]) + prints("TYPE_PACKED_COLOR_ARRAY") + prints(not x) + prints(x and false) + prints(x and true) + prints(x or false) + prints(x or true) diff --git a/modules/gdscript/tests/scripts/analyzer/features/boolean_operators_for_all_types.out b/modules/gdscript/tests/scripts/analyzer/features/boolean_operators_for_all_types.out new file mode 100644 index 0000000000..e2945c910a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/boolean_operators_for_all_types.out @@ -0,0 +1,229 @@ +GDTEST_OK +TYPE_NIL +true +false +false +false +true +TYPE_BOOL +false +false +true +true +true +TYPE_INT +false +false +true +true +true +TYPE_FLOAT +false +false +true +true +true +TYPE_STRING +false +false +true +true +true +TYPE_VECTOR2 +false +false +true +true +true +TYPE_VECTOR2I +false +false +true +true +true +TYPE_RECT2 +false +false +true +true +true +TYPE_RECT2I +false +false +true +true +true +TYPE_VECTOR3 +false +false +true +true +true +TYPE_VECTOR3I +false +false +true +true +true +TYPE_TRANSFORM2D +true +false +false +false +true +TYPE_VECTOR4 +false +false +true +true +true +TYPE_VECTOR4I +false +false +true +true +true +TYPE_PLANE +false +false +true +true +true +TYPE_QUATERNION +true +false +false +false +true +TYPE_AABB +false +false +true +true +true +TYPE_BASIS +true +false +false +false +true +TYPE_TRANSFORM3D +true +false +false +false +true +TYPE_PROJECTION +true +false +false +false +true +TYPE_COLOR +false +false +true +true +true +TYPE_STRING_NAME +false +false +true +true +true +TYPE_NODE_PATH +false +false +true +true +true +TYPE_RID +true +false +false +false +true +TYPE_OBJECT +false +false +true +true +true +TYPE_CALLABLE +false +false +true +true +true +TYPE_SIGNAL +false +false +true +true +true +TYPE_DICTIONARY +false +false +true +true +true +TYPE_ARRAY +false +false +true +true +true +TYPE_PACKED_BYTE_ARRAY +false +false +true +true +true +TYPE_PACKED_INT32_ARRAY +false +false +true +true +true +TYPE_PACKED_INT64_ARRAY +false +false +true +true +true +TYPE_PACKED_FLOAT32_ARRAY +false +false +true +true +true +TYPE_PACKED_FLOAT64_ARRAY +false +false +true +true +true +TYPE_PACKED_STRING_ARRAY +false +false +true +true +true +TYPE_PACKED_VECTOR2_ARRAY +false +false +true +true +true +TYPE_PACKED_VECTOR3_ARRAY +false +false +true +true +true +TYPE_PACKED_COLOR_ARRAY +false +false +true +true +true diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd index 26542a9e2f..c4108f50de 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd @@ -116,8 +116,8 @@ func test(): assert(duplicated_floats.get_typed_builtin() == TYPE_FLOAT) - var b_objects: Array[B] = [B.new(), null] - assert(b_objects.size() == 2) + var b_objects: Array[B] = [B.new(), B.new() as A, null] + assert(b_objects.size() == 3) assert(b_objects.get_typed_builtin() == TYPE_OBJECT) assert(b_objects.get_typed_script() == B) diff --git a/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd new file mode 100644 index 0000000000..1aacd1d11c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd @@ -0,0 +1,11 @@ +class TestOne: + func _get_property_list(): + return {} + +class TestTwo extends TestOne: + func _init(): + var _x = _get_property_list() + +func test(): + var x = TestTwo.new() + var _x = x._get_property_list() diff --git a/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.gd b/modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.gd new file mode 100644 index 0000000000..c447003619 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.gd @@ -0,0 +1,10 @@ +class TestOne: + func _init(): + pass + +class TestTwo extends TestOne: + func _init(): + super() + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.out b/modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/virtual_super_implemented.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.gd b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.gd index 71a03fbc0d..f322783776 100644 --- a/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.gd +++ b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.gd @@ -1,3 +1,3 @@ func test(): # Number separators may not be placed right next to each other. - var __ = 1__23 + var _num = 1__23 diff --git a/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.out b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.out index 71a3c2fd6a..b308994ae2 100644 --- a/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.out +++ b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.out @@ -1,2 +1,2 @@ GDTEST_PARSER_ERROR -Only one underscore can be used as a numeric separator. +Multiple underscores cannot be adjacent in a numeric literal. diff --git a/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators_after_decimal.gd b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators_after_decimal.gd new file mode 100644 index 0000000000..3140999aa9 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators_after_decimal.gd @@ -0,0 +1,3 @@ +func test(): + # Number separators may not be placed right next to each other. + var _num = 123.45__67 diff --git a/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators_after_decimal.out b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators_after_decimal.out new file mode 100644 index 0000000000..b308994ae2 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators_after_decimal.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Multiple underscores cannot be adjacent in a numeric literal. diff --git a/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.gd b/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.gd new file mode 100644 index 0000000000..cf7fb1518c --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.gd @@ -0,0 +1,17 @@ +func test(): + print(-9223372036854775808 == (1 << 63)) + print(-2) + print(- 2) + print(---2) + print(3 - 2) + print(3-2) + print(3---2) + print(-3 - 2) + print(-3 - -2) + print(-(3 - 2)-2) + print([1, 2, 3][0]-1) + var t = 1 + print(t-1) + print(-0xFF) + print(1--0xFF) + print(floor(PI-1)) diff --git a/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out b/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out new file mode 100644 index 0000000000..c5958365ec --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/number_literals_with_sign.out @@ -0,0 +1,16 @@ +GDTEST_OK +true +-2 +-2 +-2 +1 +1 +1 +-5 +-1 +-3 +0 +0 +-255 +256 +2 diff --git a/modules/gdscript/tests/scripts/parser/features/number_separators.gd b/modules/gdscript/tests/scripts/parser/features/number_separators.gd index f5f5661cae..a534c4fde1 100644 --- a/modules/gdscript/tests/scripts/parser/features/number_separators.gd +++ b/modules/gdscript/tests/scripts/parser/features/number_separators.gd @@ -1,12 +1,26 @@ func test(): # `_` can be used as a separator for numbers in GDScript. # It can be placed anywhere in the number, except at the beginning. - # Currently, GDScript in the `master` branch only allows using one separator - # per number. - # Results are assigned to variables to avoid warnings. - var __ = 1_23 - __ = 123_ # Trailing number separators are OK. - __ = 12_3 - __ = 123_456 - __ = 0x1234_5678 - __ = 0b1001_0101 + print(1_23) + print(12_3) + print(1_2_3) + print(123_) # Trailing number separators are OK. + print(123_456) + print(123_45_6_) + print("---") + print(0x1234_00ff) + print(0x1234_00_f_f_) + print(0b1001_0101) + print(0b1001_01_0_1_) + print("---") + print(-1_234.456_7) + print(-1_23_4_.4_56_7_) + print(-1_234.) + print(-1_23_4_.) + print(.456_7) + print(.4_56_7_) + print("---") + print(-1_234.5e000_3) + print(-1_23_4_.5e0_00_3_) + print(-1_234.5e+000_3) + print(-1_23_4_.5e+0_00_3_) diff --git a/modules/gdscript/tests/scripts/parser/features/number_separators.out b/modules/gdscript/tests/scripts/parser/features/number_separators.out index d73c5eb7cd..b0d2fd94fe 100644 --- a/modules/gdscript/tests/scripts/parser/features/number_separators.out +++ b/modules/gdscript/tests/scripts/parser/features/number_separators.out @@ -1 +1,24 @@ GDTEST_OK +123 +123 +123 +123 +123456 +123456 +--- +305398015 +305398015 +149 +149 +--- +-1234.4567 +-1234.4567 +-1234 +-1234 +0.4567 +0.4567 +--- +-1234500 +-1234500 +-1234500 +-1234500 diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.gd new file mode 100644 index 0000000000..83ec6573df --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.gd @@ -0,0 +1,7 @@ +class Foo: pass +class Bar extends Foo: pass +class Baz extends Foo: pass + +func test(): + var typed: Array[Bar] = [Baz.new() as Foo] + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out new file mode 100644 index 0000000000..7b9f1066b0 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out @@ -0,0 +1,4 @@ +GDTEST_RUNTIME_ERROR +>> ERROR +>> Method/function failed. +not ok diff --git a/modules/gdscript/tests/scripts/runtime/features/ctor_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/ctor_as_callable.gd new file mode 100644 index 0000000000..515efbc9ce --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/ctor_as_callable.gd @@ -0,0 +1,10 @@ +# https://github.com/godotengine/godot/issues/70319 + +class InnerClass: + pass + +func test(): + var inner_ctor : Callable = InnerClass.new + print(inner_ctor) + var native_ctor : Callable = Node.new + print(native_ctor) diff --git a/modules/gdscript/tests/scripts/runtime/features/ctor_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/ctor_as_callable.out new file mode 100644 index 0000000000..527e3e8f84 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/ctor_as_callable.out @@ -0,0 +1,3 @@ +GDTEST_OK +GDScript::new +GDScriptNativeClass::new 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 deleted file mode 100644 index 55be021a90..0000000000 --- a/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd +++ /dev/null @@ -1,14 +0,0 @@ -# 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 deleted file mode 100644 index 9d5a18da3d..0000000000 --- a/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out +++ /dev/null @@ -1,3 +0,0 @@ -GDTEST_OK -String matched StringName -StringName matched String diff --git a/modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.gd b/modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.gd new file mode 100644 index 0000000000..77d01cf00c --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.gd @@ -0,0 +1,58 @@ +# GH-77098 p.4 + +@static_unload + +class A: + class InnerClass: + pass + + enum NamedEnum { VALUE = 111 } + enum { UNNAMED_ENUM_VALUE = 222 } + const CONSTANT = 333 + static var static_var := 1 + + static func static_func() -> int: + return 444 + +class B extends A: + func test_self(): + print(self.InnerClass is GDScript) + print(self.NamedEnum) + print(self.NamedEnum.VALUE) + print(self.UNNAMED_ENUM_VALUE) + print(self.CONSTANT) + @warning_ignore("static_called_on_instance") + print(self.static_func()) + + prints("test_self before:", self.static_var) + self.static_var = 2 + prints("test_self after:", self.static_var) + +func test(): + var hard := B.new() + hard.test_self() + + print(hard.InnerClass is GDScript) + print(hard.NamedEnum) + print(hard.NamedEnum.VALUE) + print(hard.UNNAMED_ENUM_VALUE) + print(hard.CONSTANT) + @warning_ignore("static_called_on_instance") + print(hard.static_func()) + + prints("hard before:", hard.static_var) + hard.static_var = 3 + prints("hard after:", hard.static_var) + + var weak: Variant = B.new() + print(weak.InnerClass is GDScript) + print(weak.NamedEnum) + print(weak.NamedEnum.VALUE) + print(weak.UNNAMED_ENUM_VALUE) + print(weak.CONSTANT) + @warning_ignore("unsafe_method_access") + print(weak.static_func()) + + prints("weak before:", weak.static_var) + weak.static_var = 4 + prints("weak after:", weak.static_var) diff --git a/modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.out b/modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.out new file mode 100644 index 0000000000..7d7ad04df4 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/static_access_via_instance.out @@ -0,0 +1,25 @@ +GDTEST_OK +true +{ "VALUE": 111 } +111 +222 +333 +444 +test_self before: 1 +test_self after: 2 +true +{ "VALUE": 111 } +111 +222 +333 +444 +hard before: 2 +hard after: 3 +true +{ "VALUE": 111 } +111 +222 +333 +444 +weak before: 3 +weak after: 4 diff --git a/modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.gd new file mode 100644 index 0000000000..65635daa36 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.gd @@ -0,0 +1,17 @@ +# GH-41919 + +class_name TestStaticFuncAsCallable + +class InnerClass: + static func inner_my_func(): + print("inner_my_func") + +static func my_func(): + print("my_func") + +var a: Callable = TestStaticFuncAsCallable.my_func +var b: Callable = InnerClass.inner_my_func + +func test(): + a.call() + b.call() diff --git a/modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.out new file mode 100644 index 0000000000..360bb9f322 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/static_func_as_callable.out @@ -0,0 +1,3 @@ +GDTEST_OK +my_func +inner_my_func diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd index e193312381..8da8bb7e53 100644 --- a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd +++ b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd @@ -33,24 +33,24 @@ func test(): prints("perm:", perm) prints("prop:", prop) - print("other.perm:", StaticVariablesOther.perm) - print("other.prop:", StaticVariablesOther.prop) + prints("other.perm:", StaticVariablesOther.perm) + prints("other.prop:", StaticVariablesOther.prop) StaticVariablesOther.perm = 2 StaticVariablesOther.prop = "foo" - print("other.perm:", StaticVariablesOther.perm) - print("other.prop:", StaticVariablesOther.prop) + prints("other.perm:", StaticVariablesOther.perm) + prints("other.prop:", StaticVariablesOther.prop) @warning_ignore("unsafe_method_access") var path = get_script().get_path().get_base_dir() - var other = load(path + "/static_variables_load.gd") + var other = load(path + "/static_variables_load.gd") - print("load.perm:", other.perm) - print("load.prop:", other.prop) + prints("load.perm:", other.perm) + prints("load.prop:", other.prop) other.perm = 3 other.prop = "bar" - print("load.perm:", other.perm) - print("load.prop:", other.prop) + prints("load.perm:", other.perm) + prints("load.prop:", other.prop) diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables.out b/modules/gdscript/tests/scripts/runtime/features/static_variables.out index d2491aef5e..650e1d9578 100644 --- a/modules/gdscript/tests/scripts/runtime/features/static_variables.out +++ b/modules/gdscript/tests/scripts/runtime/features/static_variables.out @@ -3,14 +3,14 @@ Inner._static_init inner InnerInner._static_init inner inner data: data perm: 0 -prop: prefix Hello! suffix +prop: Hello! suffix perm: 1 prop: prefix World! suffix -other.perm:0 -other.prop:prefix Hello! suffix -other.perm:2 -other.prop:prefix foo suffix -load.perm:0 -load.prop:prefix Hello! suffix -load.perm:3 -load.prop:prefix bar suffix +other.perm: 0 +other.prop: Hello! suffix +other.perm: 2 +other.prop: prefix foo suffix +load.perm: 0 +load.prop: Hello! suffix +load.perm: 3 +load.prop: prefix bar suffix diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables_2.gd b/modules/gdscript/tests/scripts/runtime/features/static_variables_2.gd new file mode 100644 index 0000000000..7a75d119ed --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/static_variables_2.gd @@ -0,0 +1,56 @@ +@static_unload + +class A: + static var x: int = 1 + + static var y: int = 42: + set(_value): + print("The setter is NOT called on initialization.") # GH-77098 p.1 + + static func _static_init() -> void: + prints("A _static_init begin:", x) + x = -1 + prints("A _static_init end:", x) + + static func sf(p_x: int) -> void: + x = p_x + prints("sf:", x) + + # GH-77331 + func f(p_x: int) -> void: + x = p_x + prints("f:", x) + +class B extends A: + static func _static_init() -> void: + prints("B _static_init begin:", x) + x = -2 + prints("B _static_init end:", x) + + static func sg(p_x: int) -> void: + x = p_x + prints("sg:", x) + + func g(p_x: int) -> void: + x = p_x + prints("g:", x) + + func h(p_x: int) -> void: + print("h: call f(%d)" % p_x) + f(p_x) + +func test(): + prints(A.x, B.x) + A.x = 1 # GH-77098 p.2 + prints(A.x, B.x) + B.x = 2 + prints(A.x, B.x) + + A.sf(3) + B.sf(4) + B.sg(5) + + var b := B.new() + b.f(6) + b.g(7) + b.h(8) diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables_2.out b/modules/gdscript/tests/scripts/runtime/features/static_variables_2.out new file mode 100644 index 0000000000..b833911d95 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/static_variables_2.out @@ -0,0 +1,15 @@ +GDTEST_OK +A _static_init begin: 1 +A _static_init end: -1 +B _static_init begin: -1 +B _static_init end: -2 +-2 -2 +1 1 +2 2 +sf: 3 +sf: 4 +sg: 5 +f: 6 +g: 7 +h: call f(8) +f: 8 diff --git a/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd index f8bd46523e..0dd40520b0 100644 --- a/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd +++ b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd @@ -1,4 +1,8 @@ # https://github.com/godotengine/godot/issues/64171 +# https://github.com/godotengine/godot/issues/60145 + +var s = "abc" +var sn = &"abc" func test(): print("Compare ==: ", "abc" == &"abc") @@ -9,3 +13,27 @@ func test(): print("Concat: ", "abc" + &"def") print("Concat: ", &"abc" + "def") print("Concat: ", &"abc" + &"def") + + match "abc": + &"abc": + print("String matched StringName literal") + _: + print("no Match") + + match &"abc": + "abc": + print("StringName matched String literal") + _: + print("no Match") + + match "abc": + sn: + print("String matched StringName") + _: + print("no match") + + match &"abc": + s: + print("StringName matched String") + _: + print("no match") diff --git a/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out index 7e9c364b60..440b613099 100644 --- a/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out +++ b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out @@ -6,3 +6,7 @@ Compare !=: false Concat: abcdef Concat: abcdef Concat: abcdef +String matched StringName literal +StringName matched String literal +String matched StringName +StringName matched String diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp index ad38312abe..0446a7aad6 100644 --- a/modules/gdscript/tests/test_gdscript.cpp +++ b/modules/gdscript/tests/test_gdscript.cpp @@ -30,6 +30,11 @@ #include "test_gdscript.h" +#include "../gdscript_analyzer.h" +#include "../gdscript_compiler.h" +#include "../gdscript_parser.h" +#include "../gdscript_tokenizer.h" + #include "core/config/project_settings.h" #include "core/io/file_access.h" #include "core/io/file_access_pack.h" @@ -38,11 +43,6 @@ #include "core/string/string_builder.h" #include "scene/resources/packed_scene.h" -#include "modules/gdscript/gdscript_analyzer.h" -#include "modules/gdscript/gdscript_compiler.h" -#include "modules/gdscript/gdscript_parser.h" -#include "modules/gdscript/gdscript_tokenizer.h" - #ifdef TOOLS_ENABLED #include "editor/editor_settings.h" #endif diff --git a/modules/gdscript/tests/test_gdscript.h b/modules/gdscript/tests/test_gdscript.h index d719e3d94a..b39dfe2b5a 100644 --- a/modules/gdscript/tests/test_gdscript.h +++ b/modules/gdscript/tests/test_gdscript.h @@ -32,6 +32,7 @@ #define TEST_GDSCRIPT_H #include "gdscript_test_runner.h" + #include "tests/test_macros.h" namespace GDScriptTests { |
