diff options
Diffstat (limited to 'modules/gdscript')
72 files changed, 4002 insertions, 1076 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 9e974b6fdc..839aa6b3c6 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -177,7 +177,7 @@ [b]Note:[/b] Resource paths can be obtained by right clicking on a resource in the Assets Panel and choosing "Copy Path" or by dragging the file from the FileSystem dock into the script. [codeblock] # Instance a scene. - var diamond = preload("res://diamond.tscn").instance() + var diamond = preload("res://diamond.tscn").instantiate() [/codeblock] </description> </method> @@ -240,10 +240,10 @@ </method> </methods> <constants> - <constant name="PI" value="3.141593"> + <constant name="PI" value="3.14159265358979"> Constant that represents how many times the diameter of a circle fits around its perimeter. This is equivalent to [code]TAU / 2[/code]. </constant> - <constant name="TAU" value="6.283185"> + <constant name="TAU" value="6.28318530717959"> The circle constant, the circumference of the unit circle in radians. </constant> <constant name="INF" value="inf"> diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index ccc942d86b..79ec9eb65f 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -64,6 +64,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) bool in_function_args = false; bool in_member_variable = false; bool in_node_path = false; + bool in_annotation = false; bool is_hex_notation = false; bool is_bin_notation = false; bool expect_type = false; @@ -357,9 +358,18 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) in_node_path = false; } + if (!in_annotation && in_region == -1 && str[j] == '@') { + in_annotation = true; + } else if (in_region != -1 || is_a_symbol) { + in_annotation = false; + } + if (in_node_path) { next_type = NODE_PATH; color = node_path_color; + } else if (in_annotation) { + next_type = ANNOTATION; + color = annotation_color; } else if (in_keyword) { next_type = KEYWORD; color = keyword_color; @@ -485,10 +495,15 @@ void GDScriptSyntaxHighlighter::_update_cache() { /* Reserved words. */ const Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color"); + const Color control_flow_keyword_color = EDITOR_GET("text_editor/highlighting/control_flow_keyword_color"); List<String> keyword_list; gdscript->get_reserved_words(&keyword_list); for (List<String>::Element *E = keyword_list.front(); E; E = E->next()) { - keywords[E->get()] = keyword_color; + if (gdscript->is_control_flow_keyword(E->get())) { + keywords[E->get()] = control_flow_keyword_color; + } else { + keywords[E->get()] = keyword_color; + } } /* Comments */ @@ -541,19 +556,22 @@ void GDScriptSyntaxHighlighter::_update_cache() { } const String text_edit_color_theme = EditorSettings::get_singleton()->get("text_editor/theme/color_theme"); - const bool default_theme = text_edit_color_theme == "Default"; + const bool godot_2_theme = text_edit_color_theme == "Godot 2"; - if (default_theme || EditorSettings::get_singleton()->is_dark_theme()) { + if (godot_2_theme || EditorSettings::get_singleton()->is_dark_theme()) { function_definition_color = Color(0.4, 0.9, 1.0); node_path_color = Color(0.39, 0.76, 0.35); + annotation_color = Color(1.0, 0.7, 0.45); } else { function_definition_color = Color(0.0, 0.65, 0.73); node_path_color = Color(0.32, 0.55, 0.29); + annotation_color = Color(0.8, 0.5, 0.25); } EDITOR_DEF("text_editor/highlighting/gdscript/function_definition_color", function_definition_color); EDITOR_DEF("text_editor/highlighting/gdscript/node_path_color", node_path_color); - if (text_edit_color_theme == "Adaptive" || default_theme) { + EDITOR_DEF("text_editor/highlighting/gdscript/annotation_color", annotation_color); + if (text_edit_color_theme == "Default" || godot_2_theme) { EditorSettings::get_singleton()->set_initial_value( "text_editor/highlighting/gdscript/function_definition_color", function_definition_color, @@ -562,10 +580,15 @@ void GDScriptSyntaxHighlighter::_update_cache() { "text_editor/highlighting/gdscript/node_path_color", node_path_color, true); + EditorSettings::get_singleton()->set_initial_value( + "text_editor/highlighting/gdscript/annotation_color", + annotation_color, + true); } function_definition_color = EDITOR_GET("text_editor/highlighting/gdscript/function_definition_color"); node_path_color = EDITOR_GET("text_editor/highlighting/gdscript/node_path_color"); + annotation_color = EDITOR_GET("text_editor/highlighting/gdscript/annotation_color"); type_color = EDITOR_GET("text_editor/highlighting/base_type_color"); } @@ -599,6 +622,6 @@ void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, cons Ref<EditorSyntaxHighlighter> GDScriptSyntaxHighlighter::_create() const { Ref<GDScriptSyntaxHighlighter> syntax_highlighter; - syntax_highlighter.instance(); + syntax_highlighter.instantiate(); return syntax_highlighter; } diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h index 1b57cb1923..d07c182aa6 100644 --- a/modules/gdscript/editor/gdscript_highlighter.h +++ b/modules/gdscript/editor/gdscript_highlighter.h @@ -54,6 +54,7 @@ private: NONE, REGION, NODE_PATH, + ANNOTATION, SYMBOL, NUMBER, FUNCTION, @@ -72,6 +73,7 @@ private: Color number_color; Color member_color; Color node_path_color; + Color annotation_color; Color type_color; void add_color_region(const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only = false); diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h index fcf438422a..caa80fc24c 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.h +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h @@ -34,7 +34,6 @@ #include "core/templates/set.h" #include "editor/editor_translation_parser.h" #include "modules/gdscript/gdscript_parser.h" -#include "modules/regex/regex.h" class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlugin { GDCLASS(GDScriptEditorTranslationParserPlugin, EditorTranslationParserPlugin); diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index a129b73c1a..8b12b1eae4 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -36,8 +36,8 @@ #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/file_access.h" #include "core/os/os.h" #include "gdscript_analyzer.h" #include "gdscript_cache.h" @@ -45,6 +45,10 @@ #include "gdscript_parser.h" #include "gdscript_warning.h" +#ifdef TESTS_ENABLED +#include "tests/gdscript_test_runner.h" +#endif + /////////////////////////// GDScriptNativeClass::GDScriptNativeClass(const StringName &p_name) { @@ -68,19 +72,19 @@ void GDScriptNativeClass::_bind_methods() { } Variant GDScriptNativeClass::_new() { - Object *o = instance(); + Object *o = instantiate(); ERR_FAIL_COND_V_MSG(!o, Variant(), "Class type: '" + String(name) + "' is not instantiable."); - Reference *ref = Object::cast_to<Reference>(o); - if (ref) { - return REF(ref); + RefCounted *rc = Object::cast_to<RefCounted>(o); + if (rc) { + return REF(rc); } else { return o; } } -Object *GDScriptNativeClass::instance() { - return ClassDB::instance(name); +Object *GDScriptNativeClass::instantiate() { + return ClassDB::instantiate(name); } void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error) { @@ -94,11 +98,11 @@ void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance p_script->implicit_initializer->call(p_instance, nullptr, 0, r_error); } -GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Callable::CallError &r_error) { +GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error) { /* STEP 1, CREATE */ GDScriptInstance *instance = memnew(GDScriptInstance); - instance->base_ref = p_isref; + instance->base_ref_counted = p_is_ref_counted; instance->members.resize(member_indices.size()); instance->script = Ref<GDScript>(this); instance->owner = p_owner; @@ -166,13 +170,13 @@ Variant GDScript::_new(const Variant **p_args, int p_argcount, Callable::CallErr ERR_FAIL_COND_V(_baseptr->native.is_null(), Variant()); if (_baseptr->native.ptr()) { - owner = _baseptr->native->instance(); + owner = _baseptr->native->instantiate(); } else { - owner = memnew(Reference); //by default, no base means use reference + owner = memnew(RefCounted); //by default, no base means use reference } ERR_FAIL_COND_V_MSG(!owner, Variant(), "Can't inherit from a virtual class."); - Reference *r = Object::cast_to<Reference>(owner); + RefCounted *r = Object::cast_to<RefCounted>(owner); if (r) { ref = REF(r); } @@ -192,7 +196,7 @@ Variant GDScript::_new(const Variant **p_args, int p_argcount, Callable::CallErr } } -bool GDScript::can_instance() const { +bool GDScript::can_instantiate() const { #ifdef TOOLS_ENABLED return valid && (tool || ScriptServer::is_scripting_enabled()); #else @@ -276,7 +280,7 @@ void GDScript::_get_script_property_list(List<PropertyInfo> *r_list, bool p_incl } msort.sort(); - msort.invert(); + msort.reverse(); for (int i = 0; i < msort.size(); i++) { props.push_front(sptr->member_info[msort[i].name]); } @@ -342,21 +346,21 @@ ScriptInstance *GDScript::instance_create(Object *p_this) { if (top->native.is_valid()) { if (!ClassDB::is_parent_class(p_this->get_class_name(), top->native->get_name())) { if (EngineDebugger::is_active()) { - GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), 1, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); + GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), 1, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'"); } - ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instanced in object of type '" + p_this->get_class() + "'" + "."); + ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instantiated in object of type '" + p_this->get_class() + "'" + "."); } } Callable::CallError unchecked_error; - return _create_instance(nullptr, 0, p_this, Object::cast_to<Reference>(p_this) != nullptr, unchecked_error); + return _create_instance(nullptr, 0, p_this, Object::cast_to<RefCounted>(p_this) != nullptr, unchecked_error); } PlaceHolderScriptInstance *GDScript::placeholder_instance_create(Object *p_this) { #ifdef TOOLS_ENABLED PlaceHolderScriptInstance *si = memnew(PlaceHolderScriptInstance(GDScriptLanguage::get_singleton(), Ref<Script>(this), p_this)); placeholders.insert(si); - _update_exports(); + _update_exports(nullptr, false, si); return si; #else return nullptr; @@ -477,7 +481,7 @@ void GDScript::_update_doc() { methods[i].return_val.class_name = _get_gdscript_reference_class_name(Object::cast_to<GDScript>(return_type.script_type)); } - // Change class name if argumetn is script reference. + // Change class name if argument is script reference. for (int j = 0; j < fn->get_argument_count(); j++) { GDScriptDataType arg_type = fn->get_argument_type(j); if (arg_type.kind == GDScriptDataType::GDSCRIPT) { @@ -580,7 +584,7 @@ void GDScript::_update_doc() { } #endif -bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) { +bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderScriptInstance *p_instance_to_update) { #ifdef TOOLS_ENABLED static Vector<GDScript *> base_caches; @@ -717,15 +721,19 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) { } } - if (placeholders.size()) { //hm :( + if ((changed || p_instance_to_update) && placeholders.size()) { //hm :( // update placeholders if any Map<StringName, Variant> values; List<PropertyInfo> propnames; _update_exports_values(values, propnames); - for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { - E->get()->update(propnames, values); + if (changed) { + for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { + E->get()->update(propnames, values); + } + } else { + p_instance_to_update->update(propnames, values); } } @@ -893,68 +901,10 @@ void GDScript::get_members(Set<StringName> *p_members) { } } -Vector<ScriptNetData> GDScript::get_rpc_methods() const { +const Vector<MultiplayerAPI::RPCConfig> GDScript::get_rpc_methods() const { return rpc_functions; } -uint16_t GDScript::get_rpc_method_id(const StringName &p_method) const { - for (int i = 0; i < rpc_functions.size(); i++) { - if (rpc_functions[i].name == p_method) { - return i; - } - } - return UINT16_MAX; -} - -StringName GDScript::get_rpc_method(const uint16_t p_rpc_method_id) const { - if (p_rpc_method_id >= rpc_functions.size()) { - return StringName(); - } - return rpc_functions[p_rpc_method_id].name; -} - -MultiplayerAPI::RPCMode GDScript::get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const { - if (p_rpc_method_id >= rpc_functions.size()) { - return MultiplayerAPI::RPC_MODE_DISABLED; - } - return rpc_functions[p_rpc_method_id].mode; -} - -MultiplayerAPI::RPCMode GDScript::get_rpc_mode(const StringName &p_method) const { - return get_rpc_mode_by_id(get_rpc_method_id(p_method)); -} - -Vector<ScriptNetData> GDScript::get_rset_properties() const { - return rpc_variables; -} - -uint16_t GDScript::get_rset_property_id(const StringName &p_variable) const { - for (int i = 0; i < rpc_variables.size(); i++) { - if (rpc_variables[i].name == p_variable) { - return i; - } - } - return UINT16_MAX; -} - -StringName GDScript::get_rset_property(const uint16_t p_rset_member_id) const { - if (p_rset_member_id >= rpc_variables.size()) { - return StringName(); - } - return rpc_variables[p_rset_member_id].name; -} - -MultiplayerAPI::RPCMode GDScript::get_rset_mode_by_id(const uint16_t p_rset_member_id) const { - if (p_rset_member_id >= rpc_variables.size()) { - return MultiplayerAPI::RPC_MODE_DISABLED; - } - return rpc_variables[p_rset_member_id].mode; -} - -MultiplayerAPI::RPCMode GDScript::get_rset_mode(const StringName &p_variable) const { - return get_rset_mode_by_id(get_rset_property_id(p_variable)); -} - Variant GDScript::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { GDScript *top = this; while (top) { @@ -1041,10 +991,10 @@ Error GDScript::load_source_code(const String &p_path) { ERR_FAIL_COND_V(err, err); } - int len = f->get_len(); + uint64_t len = f->get_length(); sourcef.resize(len + 1); uint8_t *w = sourcef.ptrw(); - int r = f->get_buffer(w, len); + uint64_t r = f->get_buffer(w, len); f->close(); memdelete(f); ERR_FAIL_COND_V(r != len, ERR_CANT_OPEN); @@ -1206,10 +1156,8 @@ void GDScript::_save_orphaned_subclasses() { void GDScript::_init_rpc_methods_properties() { // Copy the base rpc methods so we don't mask their IDs. rpc_functions.clear(); - rpc_variables.clear(); if (base.is_valid()) { rpc_functions = base->rpc_functions; - rpc_variables = base->rpc_variables; } GDScript *cscript = this; @@ -1218,25 +1166,17 @@ void GDScript::_init_rpc_methods_properties() { // RPC Methods for (Map<StringName, GDScriptFunction *>::Element *E = cscript->member_functions.front(); E; E = E->next()) { if (E->get()->get_rpc_mode() != MultiplayerAPI::RPC_MODE_DISABLED) { - ScriptNetData nd; + MultiplayerAPI::RPCConfig nd; nd.name = E->key(); - nd.mode = E->get()->get_rpc_mode(); + nd.rpc_mode = E->get()->get_rpc_mode(); + // TODO + nd.transfer_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE; + nd.channel = 0; if (-1 == rpc_functions.find(nd)) { rpc_functions.push_back(nd); } } } - // RSet - for (Map<StringName, MemberInfo>::Element *E = cscript->member_indices.front(); E; E = E->next()) { - if (E->get().rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) { - ScriptNetData nd; - nd.name = E->key(); - nd.mode = E->get().rpc_mode; - if (-1 == rpc_variables.find(nd)) { - rpc_variables.push_back(nd); - } - } - } if (cscript != this) { sub_E = sub_E->next(); @@ -1250,8 +1190,7 @@ void GDScript::_init_rpc_methods_properties() { } // Sort so we are 100% that they are always the same. - rpc_functions.sort_custom<SortNetData>(); - rpc_variables.sort_custom<SortNetData>(); + rpc_functions.sort_custom<MultiplayerAPI::SortRPCConfig>(); } GDScript::~GDScript() { @@ -1310,21 +1249,29 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { return true; //function exists, call was successful } } else { - if (!member->data_type.is_type(p_value)) { - // Try conversion - Callable::CallError ce; - const Variant *value = &p_value; - Variant converted; - Variant::construct(member->data_type.builtin_type, converted, &value, 1, ce); - if (ce.error == Callable::CallError::CALL_OK) { - members.write[member->index] = converted; - return true; - } else { - return false; + if (member->data_type.has_type) { + if (member->data_type.builtin_type == Variant::ARRAY && member->data_type.has_container_element_type()) { + // Typed array. + if (p_value.get_type() == Variant::ARRAY) { + return VariantInternal::get_array(&members.write[member->index])->typed_assign(p_value); + } else { + return false; + } + } else if (!member->data_type.is_type(p_value)) { + // Try conversion + Callable::CallError ce; + const Variant *value = &p_value; + Variant converted; + Variant::construct(member->data_type.builtin_type, converted, &value, 1, ce); + if (ce.error == Callable::CallError::CALL_OK) { + members.write[member->index] = converted; + return true; + } else { + return false; + } } - } else { - members.write[member->index] = p_value; } + members.write[member->index] = p_value; } return true; } @@ -1494,7 +1441,7 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const } msort.sort(); - msort.invert(); + msort.reverse(); for (int i = 0; i < msort.size(); i++) { props.push_front(sptr->member_info[msort[i].name]); } @@ -1599,46 +1546,10 @@ ScriptLanguage *GDScriptInstance::get_language() { return GDScriptLanguage::get_singleton(); } -Vector<ScriptNetData> GDScriptInstance::get_rpc_methods() const { +const Vector<MultiplayerAPI::RPCConfig> GDScriptInstance::get_rpc_methods() const { return script->get_rpc_methods(); } -uint16_t GDScriptInstance::get_rpc_method_id(const StringName &p_method) const { - return script->get_rpc_method_id(p_method); -} - -StringName GDScriptInstance::get_rpc_method(const uint16_t p_rpc_method_id) const { - return script->get_rpc_method(p_rpc_method_id); -} - -MultiplayerAPI::RPCMode GDScriptInstance::get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const { - return script->get_rpc_mode_by_id(p_rpc_method_id); -} - -MultiplayerAPI::RPCMode GDScriptInstance::get_rpc_mode(const StringName &p_method) const { - return script->get_rpc_mode(p_method); -} - -Vector<ScriptNetData> GDScriptInstance::get_rset_properties() const { - return script->get_rset_properties(); -} - -uint16_t GDScriptInstance::get_rset_property_id(const StringName &p_variable) const { - return script->get_rset_property_id(p_variable); -} - -StringName GDScriptInstance::get_rset_property(const uint16_t p_rset_member_id) const { - return script->get_rset_property(p_rset_member_id); -} - -MultiplayerAPI::RPCMode GDScriptInstance::get_rset_mode_by_id(const uint16_t p_rset_member_id) const { - return script->get_rset_mode_by_id(p_rset_member_id); -} - -MultiplayerAPI::RPCMode GDScriptInstance::get_rset_mode(const StringName &p_variable) const { - return script->get_rset_mode(p_variable); -} - void GDScriptInstance::reload_members() { #ifdef DEBUG_ENABLED @@ -1669,7 +1580,7 @@ void GDScriptInstance::reload_members() { GDScriptInstance::GDScriptInstance() { owner = nullptr; - base_ref = false; + base_ref_counted = false; } GDScriptInstance::~GDScriptInstance() { @@ -1758,6 +1669,10 @@ void GDScriptLanguage::init() { for (List<Engine::Singleton>::Element *E = singletons.front(); E; E = E->next()) { _add_global(E->get().name, E->get().ptr); } + +#ifdef TESTS_ENABLED + GDScriptTests::GDScriptTestRunner::handle_cmdline(); +#endif } String GDScriptLanguage::get_type() const { @@ -2119,6 +2034,19 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { } } +bool GDScriptLanguage::is_control_flow_keyword(String p_keyword) const { + return p_keyword == "break" || + p_keyword == "continue" || + p_keyword == "elif" || + p_keyword == "else" || + p_keyword == "if" || + p_keyword == "for" || + p_keyword == "match" || + p_keyword == "pass" || + p_keyword == "return" || + p_keyword == "while"; +} + bool GDScriptLanguage::handles_global_class_type(const String &p_type) const { return p_type == "GDScript"; } @@ -2140,7 +2068,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b if (err == OK) { const GDScriptParser::ClassNode *c = parser.get_tree(); if (r_icon_path) { - if (c->icon_path.is_empty() || c->icon_path.is_abs_path()) { + if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) { *r_icon_path = c->icon_path; } else if (c->icon_path.is_rel_path()) { *r_icon_path = p_path.get_base_dir().plus_file(c->icon_path).simplify_path(); @@ -2208,7 +2136,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b break; } } else { - *r_base_type = "Reference"; + *r_base_type = "RefCounted"; subclass = nullptr; } } @@ -2293,7 +2221,7 @@ GDScriptLanguage::~GDScriptLanguage() { script->unreference(); } - singleton = NULL; + singleton = nullptr; } void GDScriptLanguage::add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass) { @@ -2328,7 +2256,7 @@ RES ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_ori if (script.is_null()) { // Don't fail loading because of parsing error. - script.instance(); + script.instantiate(); } if (r_error) { diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 12c909fd4f..078b7a2fd0 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -39,8 +39,8 @@ #include "core/object/script_language.h" #include "gdscript_function.h" -class GDScriptNativeClass : public Reference { - GDCLASS(GDScriptNativeClass, Reference); +class GDScriptNativeClass : public RefCounted { + GDCLASS(GDScriptNativeClass, RefCounted); StringName name; @@ -51,7 +51,7 @@ protected: public: _FORCE_INLINE_ const StringName &get_name() const { return name; } Variant _new(); - Object *instance(); + Object *instantiate(); GDScriptNativeClass(const StringName &p_name); }; @@ -80,14 +80,13 @@ class GDScript : public Script { GDScript *_base = nullptr; //fast pointer access GDScript *_owner = nullptr; //for subclasses - Set<StringName> members; //members are just indices to the instanced script. + Set<StringName> members; //members are just indices to the instantiated script. Map<StringName, Variant> constants; Map<StringName, GDScriptFunction *> member_functions; - Map<StringName, MemberInfo> member_indices; //members are just indices to the instanced script. + Map<StringName, MemberInfo> member_indices; //members are just indices to the instantiated script. Map<StringName, Ref<GDScript>> subclasses; Map<StringName, Vector<StringName>> _signals; - Vector<ScriptNetData> rpc_functions; - Vector<ScriptNetData> rpc_variables; + Vector<MultiplayerAPI::RPCConfig> rpc_functions; #ifdef TOOLS_ENABLED @@ -133,7 +132,7 @@ class GDScript : public Script { SelfList<GDScriptFunctionState>::List pending_func_states; void _super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error); - GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Callable::CallError &r_error); + GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error); void _set_subclass_path(Ref<GDScript> &p_sc, const String &p_path); @@ -149,7 +148,7 @@ class GDScript : public Script { #endif - bool _update_exports(bool *r_err = nullptr, bool p_recursive_call = false); + bool _update_exports(bool *r_err = nullptr, bool p_recursive_call = false, PlaceHolderScriptInstance *p_instance_to_update = nullptr); void _save_orphaned_subclasses(); void _init_rpc_methods_properties(); @@ -158,7 +157,7 @@ class GDScript : public Script { void _get_script_method_list(List<MethodInfo> *r_list, bool p_include_base) const; void _get_script_signal_list(List<MethodInfo> *r_list, bool p_include_base) const; - // This method will map the class name from "Reference" to "MyClass.InnerClass". + // This method will map the class name from "RefCounted" to "MyClass.InnerClass". static String _get_gdscript_reference_class_name(const GDScript *p_gdscript); protected: @@ -197,7 +196,7 @@ public: StringName debug_get_member_by_index(int p_idx) const; Variant _new(const Variant **p_args, int p_argcount, Callable::CallError &r_error); - virtual bool can_instance() const override; + virtual bool can_instantiate() const override; virtual Ref<Script> get_base_script() const override; @@ -247,17 +246,7 @@ public: virtual void get_constants(Map<StringName, Variant> *p_constants) override; virtual void get_members(Set<StringName> *p_members) override; - virtual Vector<ScriptNetData> get_rpc_methods() const override; - virtual uint16_t get_rpc_method_id(const StringName &p_method) const override; - virtual StringName get_rpc_method(const uint16_t p_rpc_method_id) const override; - virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const override; - virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const override; - - virtual Vector<ScriptNetData> get_rset_properties() const override; - virtual uint16_t get_rset_property_id(const StringName &p_variable) const override; - virtual StringName get_rset_property(const uint16_t p_variable_id) const override; - virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(const uint16_t p_variable_id) const override; - virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const override; + virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override; #ifdef TOOLS_ENABLED virtual bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; } @@ -270,6 +259,7 @@ public: class GDScriptInstance : public ScriptInstance { friend class GDScript; friend class GDScriptFunction; + friend class GDScriptLambdaCallable; friend class GDScriptCompiler; friend struct GDScriptUtilityFunctionsDefinitions; @@ -280,7 +270,7 @@ class GDScriptInstance : public ScriptInstance { Map<StringName, int> member_indices_cache; //used only for hot script reloading #endif Vector<Variant> members; - bool base_ref; + bool base_ref_counted; SelfList<GDScriptFunctionState>::List pending_func_states; @@ -309,17 +299,7 @@ public: void reload_members(); - virtual Vector<ScriptNetData> get_rpc_methods() const; - virtual uint16_t get_rpc_method_id(const StringName &p_method) const; - virtual StringName get_rpc_method(const uint16_t p_rpc_method_id) const; - virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const; - virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const; - - virtual Vector<ScriptNetData> get_rset_properties() const; - virtual uint16_t get_rset_property_id(const StringName &p_variable) const; - virtual StringName get_rset_property(const uint16_t p_variable_id) const; - virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(const uint16_t p_variable_id) const; - virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const; + virtual const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const; GDScriptInstance(); ~GDScriptInstance(); @@ -460,13 +440,14 @@ public: /* EDITOR FUNCTIONS */ virtual void get_reserved_words(List<String> *p_words) const; + virtual bool is_control_flow_keyword(String p_keywords) const; virtual void get_comment_delimiters(List<String> *p_delimiters) const; virtual void get_string_delimiters(List<String> *p_delimiters) const; virtual String _get_processed_template(const String &p_template, const String &p_base_class_name) const; virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const; virtual bool is_using_templates(); virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script); - virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptLanguage::Warning> *r_warnings = nullptr, Set<int> *r_safe_lines = nullptr) const; + virtual bool validate(const String &p_script, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptLanguage::ScriptError> *r_errors = nullptr, List<ScriptLanguage::Warning> *r_warnings = nullptr, Set<int> *r_safe_lines = nullptr) const; virtual Script *create_script() const; virtual bool has_named_classes() const; virtual bool supports_builtin_mode() const; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index a6138cc564..e7fb33a6a7 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -31,48 +31,14 @@ #include "gdscript_analyzer.h" #include "core/config/project_settings.h" +#include "core/io/file_access.h" #include "core/io/resource_loader.h" #include "core/object/class_db.h" #include "core/object/script_language.h" -#include "core/os/file_access.h" #include "core/templates/hash_map.h" #include "gdscript.h" #include "gdscript_utility_functions.h" -// TODO: Move this to a central location (maybe core?). -static HashMap<StringName, StringName> underscore_map; -static const char *underscore_classes[] = { - "ClassDB", - "Directory", - "Engine", - "File", - "Geometry", - "GodotSharp", - "JSON", - "Marshalls", - "Mutex", - "OS", - "ResourceLoader", - "ResourceSaver", - "Semaphore", - "Thread", - "VisualScriptEditor", - nullptr, -}; -static StringName get_real_class_name(const StringName &p_source) { - if (underscore_map.is_empty()) { - const char **class_name = underscore_classes; - while (*class_name != nullptr) { - underscore_map[*class_name] = String("_") + *class_name; - class_name++; - } - } - if (underscore_map.has(p_source)) { - return underscore_map[p_source]; - } - return p_source; -} - static MethodInfo info_from_utility_func(const StringName &p_function) { ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo()); @@ -106,10 +72,6 @@ static MethodInfo info_from_utility_func(const StringName &p_function) { return info; } -void GDScriptAnalyzer::cleanup() { - underscore_map.clear(); -} - static GDScriptParser::DataType make_callable_type(const MethodInfo &p_info) { GDScriptParser::DataType type; type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; @@ -150,7 +112,7 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_native type.is_meta_type = true; List<StringName> enum_values; - StringName real_native_name = get_real_class_name(p_native_class); + StringName real_native_name = GDScriptParser::get_real_class_name(p_native_class); ClassDB::get_enum_constants(real_native_name, p_enum_name, &enum_values); for (const List<StringName>::Element *E = enum_values.front(); E != nullptr; E = E->next()) { @@ -201,7 +163,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, if (!p_class->extends_used) { result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; result.kind = GDScriptParser::DataType::NATIVE; - result.native_type = "Reference"; + result.native_type = "RefCounted"; } else { result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; @@ -267,7 +229,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); return err; } - } else if (class_exists(name) && ClassDB::can_instance(get_real_class_name(name))) { + } else if (class_exists(name) && ClassDB::can_instantiate(GDScriptParser::get_real_class_name(name))) { base.kind = GDScriptParser::DataType::NATIVE; base.native_type = name; } else { @@ -413,6 +375,14 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = GDScriptParser::get_builtin_type(first); + + if (result.builtin_type == Variant::ARRAY) { + GDScriptParser::DataType container_type = resolve_datatype(p_type->container_type); + + if (container_type.kind != GDScriptParser::DataType::VARIANT) { + result.set_container_element_type(container_type); + } + } } else if (class_exists(first)) { // Native engine classes. result.kind = GDScriptParser::DataType::NATIVE; @@ -436,7 +406,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return GDScriptParser::DataType(); } result = ref->get_parser()->head->get_datatype(); - } else if (ClassDB::has_enum(get_real_class_name(parser->current_class->base_type.native_type), first)) { + } else if (ClassDB::has_enum(GDScriptParser::get_real_class_name(parser->current_class->base_type.native_type), first)) { // Native enum in current class. result = make_native_enum_type(parser->current_class->base_type.native_type, first); } else { @@ -499,7 +469,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } } else if (result.kind == GDScriptParser::DataType::NATIVE) { // Only enums allowed for native. - if (ClassDB::has_enum(get_real_class_name(result.native_type), p_type->type_chain[1]->name)) { + if (ClassDB::has_enum(GDScriptParser::get_real_class_name(result.native_type), p_type->type_chain[1]->name)) { if (p_type->type_chain.size() > 2) { push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[2]); } else { @@ -513,6 +483,10 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } } + if (result.builtin_type != Variant::ARRAY && p_type->container_type != nullptr) { + push_error("Only arrays can specify the collection element type.", p_type); + } + p_type->set_datatype(result); return result; } @@ -535,9 +509,23 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas datatype.kind = GDScriptParser::DataType::VARIANT; datatype.type_source = GDScriptParser::DataType::UNDETECTED; + GDScriptParser::DataType specified_type; + if (member.variable->datatype_specifier != nullptr) { + specified_type = resolve_datatype(member.variable->datatype_specifier); + specified_type.is_meta_type = false; + } + if (member.variable->initializer != nullptr) { member.variable->set_datatype(datatype); // Allow recursive usage. reduce_expression(member.variable->initializer); + if ((member.variable->infer_datatype || (member.variable->datatype_specifier != nullptr && specified_type.has_container_element_type())) && member.variable->initializer->type == GDScriptParser::Node::ARRAY) { + // Typed array. + GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(member.variable->initializer); + // Can only infer typed array if it has elements. + if ((member.variable->infer_datatype && array->elements.size() > 0) || member.variable->datatype_specifier != nullptr) { + update_array_literal_element_type(specified_type, array); + } + } datatype = member.variable->initializer->get_datatype(); if (datatype.type_source != GDScriptParser::DataType::UNDETECTED) { datatype.type_source = GDScriptParser::DataType::INFERRED; @@ -545,8 +533,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } if (member.variable->datatype_specifier != nullptr) { - datatype = resolve_datatype(member.variable->datatype_specifier); - datatype.is_meta_type = false; + datatype = specified_type; if (member.variable->initializer != nullptr) { if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) { @@ -556,6 +543,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } else { // TODO: Add warning. mark_node_unsafe(member.variable->initializer); + member.variable->use_conversion_assign = true; } } else if (datatype.builtin_type == Variant::INT && member.variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { #ifdef DEBUG_ENABLED @@ -565,6 +553,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas if (member.variable->initializer->get_datatype().is_variant()) { // TODO: Warn unsafe assign. mark_node_unsafe(member.variable->initializer); + member.variable->use_conversion_assign = true; } } } else if (member.variable->infer_datatype) { @@ -582,37 +571,32 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas datatype.is_constant = false; member.variable->set_datatype(datatype); - if (!datatype.has_no_type()) { - // TODO: Move this out into a routine specific to validate annotations. - if (member.variable->export_info.hint == PROPERTY_HINT_TYPE_STRING) { - // @export annotation. - switch (datatype.kind) { - case GDScriptParser::DataType::BUILTIN: - member.variable->export_info.hint_string = Variant::get_type_name(datatype.builtin_type); - break; - case GDScriptParser::DataType::NATIVE: - if (ClassDB::is_parent_class(get_real_class_name(datatype.native_type), "Resource")) { - member.variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; - member.variable->export_info.hint_string = get_real_class_name(datatype.native_type); - } else { - push_error(R"(Export type can only be built-in or a resource.)", member.variable); - } - break; - default: - // TODO: Allow custom user resources. - push_error(R"(Export type can only be built-in or a resource.)", member.variable); - break; - } - } + + // Apply annotations. + for (List<GDScriptParser::AnnotationNode *>::Element *E = member.variable->annotations.front(); E; E = E->next()) { + E->get()->apply(parser, member.variable); } } break; case GDScriptParser::ClassNode::Member::CONSTANT: { reduce_expression(member.constant->initializer); + GDScriptParser::DataType specified_type; + + if (member.constant->datatype_specifier != nullptr) { + specified_type = resolve_datatype(member.constant->datatype_specifier); + specified_type.is_meta_type = false; + } + GDScriptParser::DataType datatype = member.constant->get_datatype(); if (member.constant->initializer) { if (member.constant->initializer->type == GDScriptParser::Node::ARRAY) { - const_fold_array(static_cast<GDScriptParser::ArrayNode *>(member.constant->initializer)); + GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(member.constant->initializer); + const_fold_array(array); + + // Can only infer typed array if it has elements. + if (array->elements.size() > 0 || (member.constant->datatype_specifier != nullptr && specified_type.has_container_element_type())) { + update_array_literal_element_type(specified_type, array); + } } else if (member.constant->initializer->type == GDScriptParser::Node::DICTIONARY) { const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(member.constant->initializer)); } @@ -622,8 +606,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } if (member.constant->datatype_specifier != nullptr) { - datatype = resolve_datatype(member.constant->datatype_specifier); - datatype.is_meta_type = false; + datatype = specified_type; if (!is_type_compatible(datatype, member.constant->initializer->get_datatype(), true)) { push_error(vformat(R"(Value of type "%s" cannot be initialized to constant of type "%s".)", member.constant->initializer->get_datatype().to_string(), datatype.to_string()), member.constant->initializer); @@ -637,6 +620,11 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas datatype.is_constant = true; member.constant->set_datatype(datatype); + + // Apply annotations. + for (List<GDScriptParser::AnnotationNode *>::Element *E = member.constant->annotations.front(); E; E = E->next()) { + E->get()->apply(parser, member.constant); + } } break; case GDScriptParser::ClassNode::Member::SIGNAL: { for (int j = 0; j < member.signal->parameters.size(); j++) { @@ -651,6 +639,11 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas signal_type.builtin_type = Variant::SIGNAL; member.signal->set_datatype(signal_type); + + // Apply annotations. + for (List<GDScriptParser::AnnotationNode *>::Element *E = member.signal->annotations.front(); E; E = E->next()) { + E->get()->apply(parser, member.signal); + } } break; case GDScriptParser::ClassNode::Member::ENUM: { GDScriptParser::DataType enum_type; @@ -693,6 +686,11 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas current_enum = nullptr; member.m_enum->set_datatype(enum_type); + + // Apply annotations. + for (List<GDScriptParser::AnnotationNode *>::Element *E = member.m_enum->annotations.front(); E; E = E->next()) { + E->get()->apply(parser, member.m_enum); + } } break; case GDScriptParser::ClassNode::Member::FUNCTION: resolve_function_signature(member.function); @@ -761,6 +759,11 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { } resolve_function_body(member.function); + + // Apply annotations. + for (List<GDScriptParser::AnnotationNode *>::Element *E = member.function->annotations.front(); E; E = E->next()) { + E->get()->apply(parser, member.function); + } } parser->current_class = previous_class; @@ -855,6 +858,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) { case GDScriptParser::Node::DICTIONARY: case GDScriptParser::Node::GET_NODE: case GDScriptParser::Node::IDENTIFIER: + case GDScriptParser::Node::LAMBDA: case GDScriptParser::Node::LITERAL: case GDScriptParser::Node::PRELOAD: case GDScriptParser::Node::SELF: @@ -1092,8 +1096,23 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable GDScriptParser::DataType type; type.kind = GDScriptParser::DataType::VARIANT; // By default. + GDScriptParser::DataType specified_type; + if (p_variable->datatype_specifier != nullptr) { + specified_type = resolve_datatype(p_variable->datatype_specifier); + specified_type.is_meta_type = false; + } + if (p_variable->initializer != nullptr) { reduce_expression(p_variable->initializer); + if ((p_variable->infer_datatype || (p_variable->datatype_specifier != nullptr && specified_type.has_container_element_type())) && p_variable->initializer->type == GDScriptParser::Node::ARRAY) { + // Typed array. + GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(p_variable->initializer); + // Can only infer typed array if it has elements. + if ((p_variable->infer_datatype && array->elements.size() > 0) || p_variable->datatype_specifier != nullptr) { + update_array_literal_element_type(specified_type, array); + } + } + type = p_variable->initializer->get_datatype(); if (p_variable->infer_datatype) { @@ -1117,7 +1136,7 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable } if (p_variable->datatype_specifier != nullptr) { - type = resolve_datatype(p_variable->datatype_specifier); + type = specified_type; type.is_meta_type = false; if (p_variable->initializer != nullptr) { @@ -1128,6 +1147,7 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable } else { // TODO: Add warning. mark_node_unsafe(p_variable->initializer); + p_variable->use_conversion_assign = true; } #ifdef DEBUG_ENABLED } else if (type.builtin_type == Variant::INT && p_variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { @@ -1137,6 +1157,7 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable if (p_variable->initializer->get_datatype().is_variant()) { // TODO: Warn unsafe assign. mark_node_unsafe(p_variable->initializer); + p_variable->use_conversion_assign = true; } } } else if (p_variable->infer_datatype) { @@ -1362,6 +1383,12 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) { if (p_return->return_value != nullptr) { reduce_expression(p_return->return_value); + if (p_return->return_value->type == GDScriptParser::Node::ARRAY) { + // Check if assigned value is an array literal, so we can make it a typed array too if appropriate. + if (parser->current_function->get_datatype().has_container_element_type() && p_return->return_value->type == GDScriptParser::Node::ARRAY) { + update_array_literal_element_type(parser->current_function->get_datatype(), static_cast<GDScriptParser::ArrayNode *>(p_return->return_value)); + } + } result = p_return->return_value->get_datatype(); } else { // Return type is null by default. @@ -1436,6 +1463,9 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre case GDScriptParser::Node::IDENTIFIER: reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_expression)); break; + case GDScriptParser::Node::LAMBDA: + reduce_lambda(static_cast<GDScriptParser::LambdaNode *>(p_expression)); + break; case GDScriptParser::Node::LITERAL: reduce_literal(static_cast<GDScriptParser::LiteralNode *>(p_expression)); break; @@ -1498,6 +1528,52 @@ void GDScriptAnalyzer::reduce_array(GDScriptParser::ArrayNode *p_array) { p_array->set_datatype(arr_type); } +// When an array literal is stored (or passed as function argument) to a typed context, we then assume the array is typed. +// This function determines which type is that (if any). +void GDScriptAnalyzer::update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal) { + GDScriptParser::DataType array_type = p_array_literal->get_datatype(); + if (p_array_literal->elements.size() == 0) { + // Empty array literal, just make the same type as the storage. + array_type.set_container_element_type(p_base_type.get_container_element_type()); + } else { + // Check if elements match. + bool all_same_type = true; + bool all_have_type = true; + + GDScriptParser::DataType element_type; + for (int i = 0; i < p_array_literal->elements.size(); i++) { + if (i == 0) { + element_type = p_array_literal->elements[0]->get_datatype(); + } else { + GDScriptParser::DataType this_element_type = p_array_literal->elements[i]->get_datatype(); + if (this_element_type.has_no_type()) { + all_same_type = false; + all_have_type = false; + break; + } else if (element_type != this_element_type) { + if (!is_type_compatible(element_type, this_element_type, false)) { + if (is_type_compatible(this_element_type, element_type, false)) { + // This element is a super-type to the previous type, so we use the super-type. + element_type = this_element_type; + } else { + // It's incompatible. + all_same_type = false; + break; + } + } + } + } + } + if (all_same_type) { + array_type.set_container_element_type(element_type); + } else if (all_have_type) { + push_error(vformat(R"(Variant array is not compatible with an array of type "%s".)", p_base_type.get_container_element_type().to_string()), p_array_literal); + } + } + // Update the type on the value itself. + p_array_literal->set_datatype(array_type); +} + void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) { reduce_expression(p_assignment->assignee); reduce_expression(p_assignment->assigned_value); @@ -1506,40 +1582,54 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig return; } - if (p_assignment->assignee->get_datatype().is_constant) { + GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype(); + + // Check if assigned value is an array literal, so we can make it a typed array too if appropriate. + if (assignee_type.has_container_element_type() && p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY) { + update_array_literal_element_type(assignee_type, static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value)); + } + + GDScriptParser::DataType assigned_value_type = p_assignment->assigned_value->get_datatype(); + + if (assignee_type.is_constant) { push_error("Cannot assign a new value to a constant.", p_assignment->assignee); } - if (!p_assignment->assignee->get_datatype().is_variant() && !p_assignment->assigned_value->get_datatype().is_variant()) { + if (!assignee_type.is_variant() && !assigned_value_type.is_variant()) { bool compatible = true; - GDScriptParser::DataType op_type = p_assignment->assigned_value->get_datatype(); + GDScriptParser::DataType op_type = assigned_value_type; if (p_assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { - op_type = get_operation_type(p_assignment->variant_op, p_assignment->assignee->get_datatype(), p_assignment->assigned_value->get_datatype(), compatible, p_assignment->assigned_value); + op_type = get_operation_type(p_assignment->variant_op, assignee_type, assigned_value_type, compatible, p_assignment->assigned_value); } if (compatible) { - compatible = is_type_compatible(p_assignment->assignee->get_datatype(), op_type, true); + compatible = is_type_compatible(assignee_type, op_type, true); if (!compatible) { - if (p_assignment->assignee->get_datatype().is_hard_type()) { + if (assignee_type.is_hard_type()) { // Try reverse test since it can be a masked subtype. - if (!is_type_compatible(op_type, p_assignment->assignee->get_datatype(), true)) { - push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", p_assignment->assigned_value->get_datatype().to_string(), p_assignment->assignee->get_datatype().to_string()), p_assignment->assigned_value); + if (!is_type_compatible(op_type, assignee_type, true)) { + push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", assigned_value_type.to_string(), assignee_type.to_string()), p_assignment->assigned_value); } else { // TODO: Add warning. mark_node_unsafe(p_assignment); + p_assignment->use_conversion_assign = true; } } else { // TODO: Warning in this case. mark_node_unsafe(p_assignment); + p_assignment->use_conversion_assign = true; } } } else { - push_error(vformat(R"(Invalid operands "%s" and "%s" for assignment operator.)", p_assignment->assignee->get_datatype().to_string(), p_assignment->assigned_value->get_datatype().to_string()), p_assignment); + push_error(vformat(R"(Invalid operands "%s" and "%s" for assignment operator.)", assignee_type.to_string(), assigned_value_type.to_string()), p_assignment); } } - if (p_assignment->assignee->get_datatype().has_no_type() || p_assignment->assigned_value->get_datatype().is_variant()) { + if (assignee_type.has_no_type() || assigned_value_type.is_variant()) { mark_node_unsafe(p_assignment); + if (assignee_type.is_hard_type()) { + p_assignment->use_conversion_assign = true; + } } if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) { @@ -1558,7 +1648,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: { GDScriptParser::DataType id_type = identifier->variable_source->get_datatype(); if (!id_type.is_hard_type()) { - id_type = p_assignment->assigned_value->get_datatype(); + id_type = assigned_value_type; id_type.type_source = GDScriptParser::DataType::INFERRED; id_type.is_constant = false; identifier->variable_source->set_datatype(id_type); @@ -1567,7 +1657,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig case GDScriptParser::IdentifierNode::LOCAL_ITERATOR: { GDScriptParser::DataType id_type = identifier->bind_source->get_datatype(); if (!id_type.is_hard_type()) { - id_type = p_assignment->assigned_value->get_datatype(); + id_type = assigned_value_type; id_type.type_source = GDScriptParser::DataType::INFERRED; id_type.is_constant = false; identifier->variable_source->set_datatype(id_type); @@ -1579,12 +1669,10 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig } } - GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype(); - GDScriptParser::DataType assigned_type = p_assignment->assigned_value->get_datatype(); #ifdef DEBUG_ENABLED - if (p_assignment->assigned_value->type == GDScriptParser::Node::CALL && assigned_type.kind == GDScriptParser::DataType::BUILTIN && assigned_type.builtin_type == Variant::NIL) { + if (p_assignment->assigned_value->type == GDScriptParser::Node::CALL && assigned_value_type.kind == GDScriptParser::DataType::BUILTIN && assigned_value_type.builtin_type == Variant::NIL) { parser->push_warning(p_assignment->assigned_value, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_assignment->assigned_value)->function_name); - } else if (assignee_type.is_hard_type() && assignee_type.builtin_type == Variant::INT && assigned_type.builtin_type == Variant::FLOAT) { + } else if (assignee_type.is_hard_type() && assignee_type.builtin_type == Variant::INT && assigned_value_type.builtin_type == Variant::FLOAT) { parser->push_warning(p_assignment->assigned_value, GDScriptWarning::NARROWING_CONVERSION); } #endif @@ -1691,10 +1779,10 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o } else { if (p_binary_op->variant_op < Variant::OP_MAX) { bool valid = false; - result = get_operation_type(p_binary_op->variant_op, p_binary_op->left_operand->get_datatype(), right_type, valid, p_binary_op); + result = get_operation_type(p_binary_op->variant_op, left_type, right_type, valid, p_binary_op); if (!valid) { - push_error(vformat(R"(Invalid operands "%s" and "%s" for "%s" operator.)", p_binary_op->left_operand->get_datatype().to_string(), right_type.to_string(), Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op); + push_error(vformat(R"(Invalid operands "%s" and "%s" for "%s" operator.)", left_type.to_string(), right_type.to_string(), Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op); } } else { if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) { @@ -1728,8 +1816,12 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_await) { bool all_is_constant = true; + Map<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing. for (int i = 0; i < p_call->arguments.size(); i++) { reduce_expression(p_call->arguments[i]); + if (p_call->arguments[i]->type == GDScriptParser::Node::ARRAY) { + arrays[i] = static_cast<GDScriptParser::ArrayNode *>(p_call->arguments[i]); + } all_is_constant = all_is_constant && p_call->arguments[i]->is_constant; } @@ -1976,12 +2068,20 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa if (p_call->is_super) { base_type = parser->current_class->base_type; + base_type.is_meta_type = false; is_self = true; } else if (callee_type == GDScriptParser::Node::IDENTIFIER) { base_type = parser->current_class->get_datatype(); + base_type.is_meta_type = false; is_self = true; } else if (callee_type == GDScriptParser::Node::SUBSCRIPT) { GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(p_call->callee); + if (subscript->base == nullptr) { + // Invalid syntax, error already set on parser. + p_call->set_datatype(call_type); + mark_node_unsafe(p_call); + return; + } if (!subscript->is_attribute) { // Invalid call. Error already sent in parser. // TODO: Could check if Callable here. @@ -1989,9 +2089,23 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa mark_node_unsafe(p_call); return; } - reduce_expression(subscript->base); + if (subscript->attribute == nullptr) { + // Invalid call. Error already sent in parser. + p_call->set_datatype(call_type); + mark_node_unsafe(p_call); + return; + } - base_type = subscript->base->get_datatype(); + GDScriptParser::IdentifierNode *base_id = nullptr; + if (subscript->base->type == GDScriptParser::Node::IDENTIFIER) { + base_id = static_cast<GDScriptParser::IdentifierNode *>(subscript->base); + } + if (base_id && GDScriptParser::get_builtin_type(base_id->name) < Variant::VARIANT_MAX) { + base_type = make_builtin_meta_type(GDScriptParser::get_builtin_type(base_id->name)); + } else { + reduce_expression(subscript->base); + base_type = subscript->base->get_datatype(); + } } else { // Invalid call. Error already sent in parser. // TODO: Could check if Callable here too. @@ -2007,10 +2121,19 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa List<GDScriptParser::DataType> par_types; if (get_function_signature(p_call, base_type, p_call->function_name, return_type, par_types, default_arg_count, is_static, is_vararg)) { + // If the function require typed arrays we must make literals be typed. + for (Map<int, GDScriptParser::ArrayNode *>::Element *E = arrays.front(); E; E = E->next()) { + int index = E->key(); + if (index < par_types.size() && par_types[index].has_container_element_type()) { + update_array_literal_element_type(par_types[index], E->get()); + } + } validate_call_arg(par_types, default_arg_count, is_vararg, p_call); if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) { push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parser->current_function->identifier->name), p_call->callee); + } else if (is_self && !is_static && !lambda_stack.is_empty()) { + push_error(vformat(R"*(Cannot call non-static function "%s()" from a lambda function.)*", p_call->function_name), p_call->callee); } call_type = return_type; @@ -2131,8 +2254,10 @@ void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) result.native_type = "Node"; result.builtin_type = Variant::OBJECT; - if (!ClassDB::is_parent_class(get_real_class_name(parser->current_class->base_type.native_type), result.native_type)) { + if (!ClassDB::is_parent_class(GDScriptParser::get_real_class_name(parser->current_class->base_type.native_type), result.native_type)) { push_error(R"*(Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node.)*", p_get_node); + } else if (!lambda_stack.is_empty()) { + push_error(R"*(Cannot use shorthand "get_node()" notation ("$") inside a lambda. Use a captured variable instead.)*", p_get_node); } p_get_node->set_datatype(result); @@ -2224,6 +2349,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod GDScriptParser::DataType result; result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; result.kind = GDScriptParser::DataType::ENUM_VALUE; + result.builtin_type = base.builtin_type; result.native_type = base.native_type; result.enum_type = name; p_identifier->set_datatype(result); @@ -2260,6 +2386,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod case GDScriptParser::ClassNode::Member::ENUM_VALUE: p_identifier->is_constant = true; p_identifier->reduced_value = member.enum_value.value; + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; break; case GDScriptParser::ClassNode::Member::VARIABLE: p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE; @@ -2297,7 +2424,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } // Check native members. - const StringName &native = get_real_class_name(base.native_type); + const StringName &native = GDScriptParser::get_real_class_name(base.native_type); if (class_exists(native)) { PropertyInfo prop_info; @@ -2360,42 +2487,65 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident } } + bool found_source = false; // Check if identifier is local. // If that's the case, the declaration already was solved before. switch (p_identifier->source) { case GDScriptParser::IdentifierNode::FUNCTION_PARAMETER: p_identifier->set_datatype(p_identifier->parameter_source->get_datatype()); - return; + found_source = true; + break; case GDScriptParser::IdentifierNode::LOCAL_CONSTANT: case GDScriptParser::IdentifierNode::MEMBER_CONSTANT: p_identifier->set_datatype(p_identifier->constant_source->get_datatype()); p_identifier->is_constant = true; // TODO: Constant should have a value on the node itself. p_identifier->reduced_value = p_identifier->constant_source->initializer->reduced_value; - return; + found_source = true; + break; case GDScriptParser::IdentifierNode::MEMBER_VARIABLE: p_identifier->variable_source->usages++; [[fallthrough]]; case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: p_identifier->set_datatype(p_identifier->variable_source->get_datatype()); - return; + found_source = true; + break; case GDScriptParser::IdentifierNode::LOCAL_ITERATOR: p_identifier->set_datatype(p_identifier->bind_source->get_datatype()); - return; + found_source = true; + break; case GDScriptParser::IdentifierNode::LOCAL_BIND: { GDScriptParser::DataType result = p_identifier->bind_source->get_datatype(); result.is_constant = true; p_identifier->set_datatype(result); - return; - } + found_source = true; + } break; case GDScriptParser::IdentifierNode::UNDEFINED_SOURCE: break; } // Not a local, so check members. - reduce_identifier_from_base(p_identifier); - if (p_identifier->get_datatype().is_set()) { - // Found. + if (!found_source) { + reduce_identifier_from_base(p_identifier); + if (p_identifier->source != GDScriptParser::IdentifierNode::UNDEFINED_SOURCE || p_identifier->get_datatype().is_set()) { + // Found. + found_source = true; + } + } + + if (found_source) { + // If the identifier is local, check if it's any kind of capture by comparing their source function. + // Only capture locals and members and enum values. Constants are still accessible from the lambda using the script reference. + if (p_identifier->source == GDScriptParser::IdentifierNode::UNDEFINED_SOURCE || p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_CONSTANT || lambda_stack.is_empty()) { + return; + } + + GDScriptParser::FunctionNode *function_test = lambda_stack.back()->get()->function; + while (function_test != nullptr && function_test != p_identifier->source_function && function_test->source_lambda != nullptr && !function_test->source_lambda->captures_indices.has(p_identifier->name)) { + function_test->source_lambda->captures_indices[p_identifier->name] = function_test->source_lambda->captures.size(); + function_test->source_lambda->captures.push_back(p_identifier); + function_test = function_test->source_lambda->parent_function; + } return; } @@ -2477,6 +2627,57 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident p_identifier->set_datatype(dummy); // Just so type is set to something. } +void GDScriptAnalyzer::reduce_lambda(GDScriptParser::LambdaNode *p_lambda) { + // Lambda is always a Callable. + GDScriptParser::DataType lambda_type; + lambda_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + lambda_type.kind = GDScriptParser::DataType::BUILTIN; + lambda_type.builtin_type = Variant::CALLABLE; + p_lambda->set_datatype(lambda_type); + + if (p_lambda->function == nullptr) { + return; + } + + GDScriptParser::FunctionNode *previous_function = parser->current_function; + parser->current_function = p_lambda->function; + + lambda_stack.push_back(p_lambda); + + for (int i = 0; i < p_lambda->function->parameters.size(); i++) { + resolve_parameter(p_lambda->function->parameters[i]); + } + + resolve_suite(p_lambda->function->body); + + int captures_amount = p_lambda->captures.size(); + if (captures_amount > 0) { + // Create space for lambda parameters. + // At the beginning to not mess with optional parameters. + int param_count = p_lambda->function->parameters.size(); + p_lambda->function->parameters.resize(param_count + captures_amount); + for (int i = param_count - 1; i >= 0; i--) { + p_lambda->function->parameters.write[i + captures_amount] = p_lambda->function->parameters[i]; + p_lambda->function->parameters_indices[p_lambda->function->parameters[i]->identifier->name] = i + captures_amount; + } + + // Add captures as extra parameters at the beginning. + for (int i = 0; i < p_lambda->captures.size(); i++) { + GDScriptParser::IdentifierNode *capture = p_lambda->captures[i]; + GDScriptParser::ParameterNode *capture_param = parser->alloc_node<GDScriptParser::ParameterNode>(); + capture_param->identifier = capture; + capture_param->usages = capture->usages; + capture_param->set_datatype(capture->get_datatype()); + + p_lambda->function->parameters.write[i] = capture_param; + p_lambda->function->parameters_indices[capture->name] = i; + } + } + + lambda_stack.pop_back(); + parser->current_function = previous_function; +} + void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) { p_literal->reduced_value = p_literal->value; p_literal->is_constant = true; @@ -2535,25 +2736,6 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri GDScriptParser::DataType result_type; - // Reduce index first. If it's a constant StringName, use attribute instead. - if (!p_subscript->is_attribute) { - if (p_subscript->index == nullptr) { - return; - } - reduce_expression(p_subscript->index); - - if (p_subscript->index->is_constant && p_subscript->index->reduced_value.get_type() == Variant::STRING_NAME) { - GDScriptParser::IdentifierNode *attribute = parser->alloc_node<GDScriptParser::IdentifierNode>(); - // Copy location for better error message. - attribute->start_line = p_subscript->index->start_line; - attribute->end_line = p_subscript->index->end_line; - attribute->leftmost_column = p_subscript->index->leftmost_column; - attribute->rightmost_column = p_subscript->index->rightmost_column; - p_subscript->is_attribute = true; - p_subscript->attribute = attribute; - } - } - if (p_subscript->is_attribute) { if (p_subscript->attribute == nullptr) { return; @@ -2596,7 +2778,10 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri } } } else { - // Index was already reduced before. + if (p_subscript->index == nullptr) { + return; + } + reduce_expression(p_subscript->index); if (p_subscript->base->is_constant && p_subscript->index->is_constant) { // Just try to get it. @@ -2641,7 +2826,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::RECT2: case Variant::RECT2I: case Variant::PLANE: - case Variant::QUAT: + case Variant::QUATERNION: case Variant::AABB: case Variant::OBJECT: error = index_type.builtin_type != Variant::STRING; @@ -2652,8 +2837,8 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::VECTOR2I: case Variant::VECTOR3: case Variant::VECTOR3I: - case Variant::TRANSFORM: case Variant::TRANSFORM2D: + case Variant::TRANSFORM3D: error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT && index_type.builtin_type != Variant::STRING; break; @@ -2720,7 +2905,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::PACKED_FLOAT64_ARRAY: case Variant::VECTOR2: case Variant::VECTOR3: - case Variant::QUAT: + case Variant::QUATERNION: result_type.builtin_type = Variant::FLOAT; break; // Return Color. @@ -2749,14 +2934,23 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri result_type.builtin_type = Variant::VECTOR3; break; // Depends on the index. - case Variant::TRANSFORM: + case Variant::TRANSFORM3D: case Variant::PLANE: case Variant::COLOR: - case Variant::ARRAY: case Variant::DICTIONARY: result_type.kind = GDScriptParser::DataType::VARIANT; result_type.type_source = GDScriptParser::DataType::UNDETECTED; break; + // Can have an element type. + case Variant::ARRAY: + if (base_type.has_container_element_type()) { + result_type = base_type.get_container_element_type(); + result_type.type_source = base_type.type_source; + } else { + result_type.kind = GDScriptParser::DataType::VARIANT; + result_type.type_source = GDScriptParser::DataType::UNDETECTED; + } + break; // Here for completeness. case Variant::OBJECT: case Variant::VARIANT_MAX: @@ -2979,6 +3173,34 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo result.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; } else { result.kind = GDScriptParser::DataType::BUILTIN; + result.builtin_type = p_property.type; + if (p_property.type == Variant::ARRAY && p_property.hint == PROPERTY_HINT_ARRAY_TYPE) { + // Check element type. + StringName elem_type_name = p_property.hint_string; + GDScriptParser::DataType elem_type; + elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + + Variant::Type elem_builtin_type = GDScriptParser::get_builtin_type(elem_type_name); + if (elem_builtin_type < Variant::VARIANT_MAX) { + // Builtin type. + elem_type.kind = GDScriptParser::DataType::BUILTIN; + elem_type.builtin_type = elem_builtin_type; + } else if (class_exists(elem_type_name)) { + elem_type.kind = GDScriptParser::DataType::NATIVE; + elem_type.builtin_type = Variant::OBJECT; + elem_type.native_type = p_property.hint_string; + } else if (ScriptServer::is_global_class(elem_type_name)) { + // Just load this as it shouldn't be a GDScript. + Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(elem_type_name)); + elem_type.kind = GDScriptParser::DataType::SCRIPT; + elem_type.builtin_type = Variant::OBJECT; + elem_type.native_type = script->get_instance_base_type(); + elem_type.script_type = script; + } else { + ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed array."); + } + result.set_container_element_type(elem_type); + } } return result; } @@ -3084,7 +3306,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD return true; } - StringName real_native = get_real_class_name(base_native); + StringName real_native = GDScriptParser::get_real_class_name(base_native); MethodInfo info; if (ClassDB::get_method_info(real_native, function_name, &info)) { @@ -3179,7 +3401,7 @@ bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con StringName parent = base_native; while (parent != StringName()) { - StringName real_class_name = get_real_class_name(parent); + StringName real_class_name = GDScriptParser::get_real_class_name(parent); if (ClassDB::has_method(real_class_name, name, true)) { parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "method", parent); return true; @@ -3225,6 +3447,7 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator } r_valid = true; + result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = Variant::get_operator_return_type(p_operation, a_type, b_type); @@ -3257,6 +3480,18 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ // Enum value is also integer. valid = true; } + if (valid && p_target.builtin_type == Variant::ARRAY && p_source.builtin_type == Variant::ARRAY) { + // Check the element type. + if (p_target.has_container_element_type()) { + if (!p_source.has_container_element_type()) { + // TODO: Maybe this is valid but unsafe? + // Variant array can't be appended to typed array. + valid = false; + } else { + valid = is_type_compatible(p_target.get_container_element_type(), p_source.get_container_element_type(), false); + } + } + } return valid; } @@ -3329,14 +3564,14 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ } // Get underscore-prefixed version for some classes. - src_native = get_real_class_name(src_native); + src_native = GDScriptParser::get_real_class_name(src_native); switch (p_target.kind) { case GDScriptParser::DataType::NATIVE: { if (p_target.is_meta_type) { return ClassDB::is_parent_class(src_native, GDScriptNativeClass::get_class_static()); } - StringName tgt_native = get_real_class_name(p_target.native_type); + StringName tgt_native = GDScriptParser::get_real_class_name(p_target.native_type); return ClassDB::is_parent_class(src_native, tgt_native); } case GDScriptParser::DataType::SCRIPT: @@ -3385,8 +3620,8 @@ void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) { #endif } -bool GDScriptAnalyzer::class_exists(const StringName &p_class) { - StringName real_name = get_real_class_name(p_class); +bool GDScriptAnalyzer::class_exists(const StringName &p_class) const { + StringName real_name = GDScriptParser::get_real_class_name(p_class); return ClassDB::class_exists(real_name) && ClassDB::is_class_exposed(real_name); } diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index dab5b032a3..8cd3fcf837 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -32,7 +32,7 @@ #define GDSCRIPT_ANALYZER_H #include "core/object/object.h" -#include "core/object/reference.h" +#include "core/object/ref_counted.h" #include "core/templates/set.h" #include "gdscript_cache.h" #include "gdscript_parser.h" @@ -42,6 +42,7 @@ class GDScriptAnalyzer { HashMap<String, Ref<GDScriptParserRef>> depended_parsers; const GDScriptParser::EnumNode *current_enum = nullptr; + List<const GDScriptParser::LambdaNode *> lambda_stack; Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true); GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type); @@ -82,6 +83,7 @@ class GDScriptAnalyzer { void reduce_get_node(GDScriptParser::GetNodeNode *p_get_node); void reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin = false); void reduce_identifier_from_base(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType *p_base = nullptr); + void reduce_lambda(GDScriptParser::LambdaNode *p_lambda); void reduce_literal(GDScriptParser::LiteralNode *p_literal); void reduce_preload(GDScriptParser::PreloadNode *p_preload); void reduce_self(GDScriptParser::SelfNode *p_self); @@ -103,10 +105,11 @@ class GDScriptAnalyzer { bool 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); GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source); + void update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal); bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false) const; void push_error(const String &p_message, const GDScriptParser::Node *p_origin); void mark_node_unsafe(const GDScriptParser::Node *p_node); - bool class_exists(const StringName &p_class); + bool class_exists(const StringName &p_class) const; Ref<GDScriptParserRef> get_parser_for(const String &p_path); #ifdef DEBUG_ENABLED bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context); @@ -119,8 +122,6 @@ public: Error analyze(); GDScriptAnalyzer(GDScriptParser *p_parser); - - static void cleanup(); }; #endif // GDSCRIPT_ANALYZER_H diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 58c6b31a77..5a297cc50a 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -47,7 +47,8 @@ uint32_t GDScriptByteCodeGenerator::add_parameter(const StringName &p_name, bool } uint32_t GDScriptByteCodeGenerator::add_local(const StringName &p_name, const GDScriptDataType &p_type) { - int stack_pos = increase_stack(); + int stack_pos = locals.size() + RESERVED_STACK; + locals.push_back(StackSlot(p_type.builtin_type)); add_stack_identifier(p_name, stack_pos); return stack_pos; } @@ -59,37 +60,88 @@ uint32_t GDScriptByteCodeGenerator::add_local_constant(const StringName &p_name, } uint32_t GDScriptByteCodeGenerator::add_or_get_constant(const Variant &p_constant) { - if (constant_map.has(p_constant)) { - return constant_map[p_constant]; - } - int index = constant_map.size(); - constant_map[p_constant] = index; - return index; + return get_constant_pos(p_constant); } uint32_t GDScriptByteCodeGenerator::add_or_get_name(const StringName &p_name) { return get_name_map_pos(p_name); } -uint32_t GDScriptByteCodeGenerator::add_temporary() { - current_temporaries++; - int idx = increase_stack(); -#ifdef DEBUG_ENABLED - temp_stack.push_back(idx); -#endif - return idx; +uint32_t GDScriptByteCodeGenerator::add_temporary(const GDScriptDataType &p_type) { + Variant::Type temp_type = Variant::NIL; + if (p_type.has_type) { + if (p_type.kind == GDScriptDataType::BUILTIN) { + switch (p_type.builtin_type) { + case Variant::NIL: + case Variant::BOOL: + case Variant::INT: + case Variant::FLOAT: + case Variant::STRING: + case Variant::VECTOR2: + case Variant::VECTOR2I: + case Variant::RECT2: + case Variant::RECT2I: + case Variant::VECTOR3: + case Variant::VECTOR3I: + case Variant::TRANSFORM2D: + case Variant::PLANE: + case Variant::QUATERNION: + case Variant::AABB: + case Variant::BASIS: + case Variant::TRANSFORM3D: + case Variant::COLOR: + case Variant::STRING_NAME: + case Variant::NODE_PATH: + case Variant::RID: + case Variant::OBJECT: + case Variant::CALLABLE: + case Variant::SIGNAL: + case Variant::DICTIONARY: + case Variant::ARRAY: + temp_type = p_type.builtin_type; + break; + case Variant::PACKED_BYTE_ARRAY: + case Variant::PACKED_INT32_ARRAY: + case Variant::PACKED_INT64_ARRAY: + case Variant::PACKED_FLOAT32_ARRAY: + case Variant::PACKED_FLOAT64_ARRAY: + case Variant::PACKED_STRING_ARRAY: + case Variant::PACKED_VECTOR2_ARRAY: + case Variant::PACKED_VECTOR3_ARRAY: + case Variant::PACKED_COLOR_ARRAY: + case Variant::VARIANT_MAX: + // Packed arrays are reference counted, so we don't use the pool for them. + temp_type = Variant::NIL; + break; + } + } else { + temp_type = Variant::OBJECT; + } + } + + if (!temporaries_pool.has(temp_type)) { + temporaries_pool[temp_type] = List<int>(); + } + + List<int> &pool = temporaries_pool[temp_type]; + if (pool.is_empty()) { + StackSlot new_temp(temp_type); + int idx = temporaries.size(); + pool.push_back(idx); + temporaries.push_back(new_temp); + } + int slot = pool.front()->get(); + pool.pop_front(); + used_temporaries.push_back(slot); + return slot; } void GDScriptByteCodeGenerator::pop_temporary() { - ERR_FAIL_COND(current_temporaries == 0); - current_stack_size--; -#ifdef DEBUG_ENABLED - if (temp_stack.back()->get() != current_stack_size) { - ERR_PRINT("Mismatched popping of temporary value"); - } - temp_stack.pop_back(); -#endif - current_temporaries--; + ERR_FAIL_COND(used_temporaries.is_empty()); + int slot_idx = used_temporaries.back()->get(); + const StackSlot &slot = temporaries[slot_idx]; + temporaries_pool[slot.type].push_back(slot_idx); + used_temporaries.pop_back(); } void GDScriptByteCodeGenerator::start_parameters() { @@ -100,7 +152,7 @@ void GDScriptByteCodeGenerator::start_parameters() { } void GDScriptByteCodeGenerator::end_parameters() { - function->default_arguments.invert(); + function->default_arguments.reverse(); } void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCMode p_rpc_mode, const GDScriptDataType &p_return_type) { @@ -124,12 +176,22 @@ void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName GDScriptFunction *GDScriptByteCodeGenerator::write_end() { #ifdef DEBUG_ENABLED - if (current_temporaries != 0) { - ERR_PRINT("Non-zero temporary variables at end of function: " + itos(current_temporaries)); + if (!used_temporaries.is_empty()) { + ERR_PRINT("Non-zero temporary variables at end of function: " + itos(used_temporaries.size())); } #endif append(GDScriptFunction::OPCODE_END, 0); + for (int i = 0; i < temporaries.size(); i++) { + int stack_index = i + max_locals + RESERVED_STACK; + for (int j = 0; j < temporaries[i].bytecode_indices.size(); j++) { + opcodes.write[temporaries[i].bytecode_indices[j]] = stack_index | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); + } + if (temporaries[i].type != Variant::NIL) { + function->temporary_slots[stack_index] = temporaries[i].type; + } + } + if (constant_map.size()) { function->_constant_count = constant_map.size(); function->constants.resize(constant_map.size()); @@ -319,10 +381,22 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->_methods_count = 0; } + if (lambdas_map.size()) { + function->lambdas.resize(lambdas_map.size()); + function->_lambdas_ptr = function->lambdas.ptrw(); + function->_lambdas_count = lambdas_map.size(); + for (const Map<GDScriptFunction *, int>::Element *E = lambdas_map.front(); E; E = E->next()) { + function->lambdas.write[E->get()] = E->key(); + } + } else { + function->_lambdas_ptr = nullptr; + function->_lambdas_count = 0; + } + if (debug_stack) { function->stack_debug = stack_debug; } - function->_stack_size = stack_max; + function->_stack_size = RESERVED_STACK + max_locals + temporaries.size(); function->_instruction_args_size = instr_args_max; function->_ptrcall_args_size = ptrcall_max; @@ -346,6 +420,117 @@ void GDScriptByteCodeGenerator::set_initial_line(int p_line) { #define IS_BUILTIN_TYPE(m_var, m_type) \ (m_var.type.has_type && m_var.type.kind == GDScriptDataType::BUILTIN && m_var.type.builtin_type == m_type) +void GDScriptByteCodeGenerator::write_type_adjust(const Address &p_target, Variant::Type p_new_type) { + switch (p_new_type) { + case Variant::BOOL: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_BOOL, 1); + break; + case Variant::INT: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_INT, 1); + break; + case Variant::FLOAT: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_FLOAT, 1); + break; + case Variant::STRING: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_STRING, 1); + break; + case Variant::VECTOR2: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR2, 1); + break; + case Variant::VECTOR2I: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR2I, 1); + break; + case Variant::RECT2: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_RECT2, 1); + break; + case Variant::RECT2I: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_RECT2I, 1); + break; + case Variant::VECTOR3: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR3, 1); + break; + case Variant::VECTOR3I: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR3I, 1); + break; + case Variant::TRANSFORM2D: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_TRANSFORM2D, 1); + break; + case Variant::PLANE: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PLANE, 1); + break; + case Variant::QUATERNION: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_QUATERNION, 1); + break; + case Variant::AABB: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_AABB, 1); + break; + case Variant::BASIS: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_BASIS, 1); + break; + case Variant::TRANSFORM3D: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_TRANSFORM, 1); + break; + case Variant::COLOR: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_COLOR, 1); + break; + case Variant::STRING_NAME: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_STRING_NAME, 1); + break; + case Variant::NODE_PATH: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_NODE_PATH, 1); + break; + case Variant::RID: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_RID, 1); + break; + case Variant::OBJECT: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_OBJECT, 1); + break; + case Variant::CALLABLE: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_CALLABLE, 1); + break; + case Variant::SIGNAL: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_SIGNAL, 1); + break; + case Variant::DICTIONARY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_DICTIONARY, 1); + break; + case Variant::ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_ARRAY, 1); + break; + case Variant::PACKED_BYTE_ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_BYTE_ARRAY, 1); + break; + case Variant::PACKED_INT32_ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_INT32_ARRAY, 1); + break; + case Variant::PACKED_INT64_ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_INT64_ARRAY, 1); + break; + case Variant::PACKED_FLOAT32_ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_FLOAT32_ARRAY, 1); + break; + case Variant::PACKED_FLOAT64_ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_FLOAT64_ARRAY, 1); + break; + case Variant::PACKED_STRING_ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_STRING_ARRAY, 1); + break; + case Variant::PACKED_VECTOR2_ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_VECTOR2_ARRAY, 1); + break; + case Variant::PACKED_VECTOR3_ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_VECTOR3_ARRAY, 1); + break; + case Variant::PACKED_COLOR_ARRAY: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_COLOR_ARRAY, 1); + break; + case Variant::NIL: + case Variant::VARIANT_MAX: + return; + } + append(p_target); +} + void GDScriptByteCodeGenerator::write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) { if (HAS_BUILTIN_TYPE(p_left_operand)) { // Gather specific operator. @@ -369,6 +554,14 @@ void GDScriptByteCodeGenerator::write_unary_operator(const Address &p_target, Va void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) { if (HAS_BUILTIN_TYPE(p_left_operand) && HAS_BUILTIN_TYPE(p_right_operand)) { + if (p_target.mode == Address::TEMPORARY) { + Variant::Type result_type = Variant::get_operator_return_type(p_operator, p_left_operand.type.builtin_type, p_right_operand.type.builtin_type); + Variant::Type temp_type = temporaries[p_target.address].type; + if (result_type != temp_type) { + write_type_adjust(p_target, result_type); + } + } + // Gather specific operator. Variant::ValidatedOperatorEvaluator op_func = Variant::get_validated_operator_evaluator(p_operator, p_left_operand.type.builtin_type, p_right_operand.type.builtin_type); @@ -396,7 +589,7 @@ void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const A } void GDScriptByteCodeGenerator::write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) { - append(GDScriptFunction::OPCODE_IS_BUILTIN, 3); + append(GDScriptFunction::OPCODE_IS_BUILTIN, 2); append(p_source); append(p_target); append(p_type); @@ -594,53 +787,43 @@ void GDScriptByteCodeGenerator::write_get_member(const Address &p_target, const append(p_name); } -void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Address &p_source) { - if (p_target.type.has_type && !p_source.type.has_type) { - // Typed assignment. - switch (p_target.type.kind) { - case GDScriptDataType::BUILTIN: { - append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2); - append(p_target); - append(p_source); - append(p_target.type.builtin_type); - } break; - case GDScriptDataType::NATIVE: { - int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_target.type.native_type]; - class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); - append(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE, 3); - append(p_target); - append(p_source); - append(class_idx); - } break; - case GDScriptDataType::SCRIPT: - case GDScriptDataType::GDSCRIPT: { - Variant script = p_target.type.script_type; - int idx = get_constant_pos(script); - idx |= (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); - - append(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT, 3); +void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_target, const Address &p_source) { + switch (p_target.type.kind) { + case GDScriptDataType::BUILTIN: { + if (p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) { + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY, 2); append(p_target); append(p_source); - append(idx); - } break; - default: { - ERR_PRINT("Compiler bug: unresolved assign."); - - // Shouldn't get here, but fail-safe to a regular assignment - append(GDScriptFunction::OPCODE_ASSIGN, 2); + } else { + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2); append(p_target); append(p_source); + append(p_target.type.builtin_type); } - } - } else { - if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) { - // Need conversion.. - append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2); + } break; + case GDScriptDataType::NATIVE: { + int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_target.type.native_type]; + Variant nc = GDScriptLanguage::get_singleton()->get_global_array()[class_idx]; + class_idx = get_constant_pos(nc) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE, 3); append(p_target); append(p_source); - append(p_target.type.builtin_type); - } else { - // Either untyped assignment or already type-checked by the parser + append(class_idx); + } break; + case GDScriptDataType::SCRIPT: + case GDScriptDataType::GDSCRIPT: { + Variant script = p_target.type.script_type; + int idx = get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); + + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT, 3); + append(p_target); + append(p_source); + append(idx); + } break; + default: { + ERR_PRINT("Compiler bug: unresolved assign."); + + // Shouldn't get here, but fail-safe to a regular assignment append(GDScriptFunction::OPCODE_ASSIGN, 2); append(p_target); append(p_source); @@ -648,6 +831,24 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr } } +void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Address &p_source) { + if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) { + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY, 2); + append(p_target); + append(p_source); + } else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) { + // Need conversion. + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2); + append(p_target); + append(p_source); + append(p_target.type.builtin_type); + } else { + append(GDScriptFunction::OPCODE_ASSIGN, 2); + append(p_target); + append(p_source); + } +} + void GDScriptByteCodeGenerator::write_assign_true(const Address &p_target) { append(GDScriptFunction::OPCODE_ASSIGN_TRUE, 1); append(p_target); @@ -663,6 +864,12 @@ void GDScriptByteCodeGenerator::write_assign_default_parameter(const Address &p_ function->default_arguments.push_back(opcodes.size()); } +void GDScriptByteCodeGenerator::write_store_named_global(const Address &p_dst, const StringName &p_global) { + append(GDScriptFunction::OPCODE_STORE_NAMED_GLOBAL, 1); + append(p_dst); + append(p_global); +} + void GDScriptByteCodeGenerator::write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) { int index = 0; @@ -673,16 +880,14 @@ void GDScriptByteCodeGenerator::write_cast(const Address &p_target, const Addres } break; case GDScriptDataType::NATIVE: { int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_type.native_type]; - class_idx |= (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); + Variant nc = GDScriptLanguage::get_singleton()->get_global_array()[class_idx]; append(GDScriptFunction::OPCODE_CAST_TO_NATIVE, 3); - index = class_idx; + index = get_constant_pos(nc) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); } break; case GDScriptDataType::SCRIPT: case GDScriptDataType::GDSCRIPT: { Variant script = p_type.script_type; - int idx = get_constant_pos(script); - idx |= (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); - + int idx = get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); append(GDScriptFunction::OPCODE_CAST_TO_SCRIPT, 3); index = idx; } break; @@ -797,6 +1002,14 @@ void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target, return; } + if (p_target.mode == Address::TEMPORARY) { + Variant::Type result_type = Variant::get_builtin_method_return_type(p_type, p_method); + Variant::Type temp_type = temporaries[p_target.address].type; + if (result_type != temp_type) { + write_type_adjust(p_target, result_type); + } + } + append(GDScriptFunction::OPCODE_CALL_BUILTIN_TYPE_VALIDATED, 2 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { @@ -808,6 +1021,56 @@ void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target, append(Variant::get_validated_builtin_method(p_type, p_method)); } +void GDScriptByteCodeGenerator::write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) { + bool is_validated = false; + + // Check if all types are correct. + if (Variant::is_builtin_method_vararg(p_type, p_method)) { + is_validated = true; // Vararg works fine with any argument, since they can be any type. + } else if (p_arguments.size() == Variant::get_builtin_method_argument_count(p_type, p_method)) { + bool all_types_exact = true; + for (int i = 0; i < p_arguments.size(); i++) { + if (!IS_BUILTIN_TYPE(p_arguments[i], Variant::get_builtin_method_argument_type(p_type, p_method, i))) { + all_types_exact = false; + break; + } + } + + is_validated = all_types_exact; + } + + if (!is_validated) { + // Perform regular call. + append(GDScriptFunction::OPCODE_CALL_BUILTIN_STATIC, p_arguments.size() + 1); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + append(p_type); + append(p_method); + append(p_arguments.size()); + return; + } + + if (p_target.mode == Address::TEMPORARY) { + Variant::Type result_type = Variant::get_builtin_method_return_type(p_type, p_method); + Variant::Type temp_type = temporaries[p_target.address].type; + if (result_type != temp_type) { + write_type_adjust(p_target, result_type); + } + } + + append(GDScriptFunction::OPCODE_CALL_BUILTIN_TYPE_VALIDATED, 2 + p_arguments.size()); + + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(Address()); // No base since it's static. + append(p_target); + append(p_arguments.size()); + append(Variant::get_validated_builtin_method(p_type, p_method)); +} + void GDScriptByteCodeGenerator::write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) { append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL_METHOD_BIND : GDScriptFunction::OPCODE_CALL_METHOD_BIND_RET, 2 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { @@ -845,12 +1108,12 @@ void GDScriptByteCodeGenerator::write_call_ptrcall(const Address &p_target, cons CASE_TYPE(PLANE); CASE_TYPE(AABB); CASE_TYPE(BASIS); - CASE_TYPE(TRANSFORM); + CASE_TYPE(TRANSFORM3D); CASE_TYPE(COLOR); CASE_TYPE(STRING_NAME); CASE_TYPE(NODE_PATH); CASE_TYPE(RID); - CASE_TYPE(QUAT); + CASE_TYPE(QUATERNION); CASE_TYPE(OBJECT); CASE_TYPE(CALLABLE); CASE_TYPE(SIGNAL); @@ -893,7 +1156,7 @@ void GDScriptByteCodeGenerator::write_call_self(const Address &p_target, const S for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); + append(GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); append(p_target); append(p_arguments.size()); append(p_function_name); @@ -904,7 +1167,7 @@ void GDScriptByteCodeGenerator::write_call_self_async(const Address &p_target, c for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS); + append(GDScriptFunction::ADDR_SELF); append(p_target); append(p_arguments.size()); append(p_function_name); @@ -921,6 +1184,17 @@ void GDScriptByteCodeGenerator::write_call_script_function(const Address &p_targ append(p_function_name); } +void GDScriptByteCodeGenerator::write_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures) { + append(GDScriptFunction::OPCODE_CREATE_LAMBDA, 1 + p_captures.size()); + for (int i = 0; i < p_captures.size(); i++) { + append(p_captures[i]); + } + + append(p_target); + append(p_captures.size()); + append(p_function); +} + void GDScriptByteCodeGenerator::write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) { // Try to find an appropriate constructor. bool all_have_type = true; @@ -980,6 +1254,25 @@ void GDScriptByteCodeGenerator::write_construct_array(const Address &p_target, c append(p_arguments.size()); } +void GDScriptByteCodeGenerator::write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) { + append(GDScriptFunction::OPCODE_CONSTRUCT_TYPED_ARRAY, 2 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + if (p_element_type.script_type) { + Variant script_type = Ref<Script>(p_element_type.script_type); + int addr = get_constant_pos(script_type); + addr |= GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS; + append(addr); + } else { + append(Address()); // null. + } + append(p_arguments.size()); + append(p_element_type.builtin_type); + append(p_element_type.native_type); +} + void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) { append(GDScriptFunction::OPCODE_CONSTRUCT_DICTIONARY, 1 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { @@ -1257,8 +1550,84 @@ void GDScriptByteCodeGenerator::write_newline(int p_line) { } void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) { - append(GDScriptFunction::OPCODE_RETURN, 1); - append(p_return_value); + if (!function->return_type.has_type || p_return_value.type.has_type) { + // Either the function is untyped or the return value is also typed. + + // If this is a typed function, then we need to check for potential conversions. + if (function->return_type.has_type) { + if (function->return_type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type == Variant::ARRAY && function->return_type.has_container_element_type()) { + // Typed array. + const GDScriptDataType &element_type = function->return_type.get_container_element_type(); + + Variant script = function->return_type.script_type; + int script_idx = get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); + + append(GDScriptFunction::OPCODE_RETURN_TYPED_ARRAY, 2); + append(p_return_value); + append(script_idx); + append(element_type.kind == GDScriptDataType::BUILTIN ? element_type.builtin_type : Variant::OBJECT); + append(element_type.native_type); + } else if (function->return_type.kind == GDScriptDataType::BUILTIN && p_return_value.type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type != p_return_value.type.builtin_type) { + // Add conversion. + append(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN, 1); + append(p_return_value); + append(function->return_type.builtin_type); + } else { + // Just assign. + append(GDScriptFunction::OPCODE_RETURN, 1); + append(p_return_value); + } + } else { + append(GDScriptFunction::OPCODE_RETURN, 1); + append(p_return_value); + } + } else { + switch (function->return_type.kind) { + case GDScriptDataType::BUILTIN: { + if (function->return_type.builtin_type == Variant::ARRAY && function->return_type.has_container_element_type()) { + const GDScriptDataType &element_type = function->return_type.get_container_element_type(); + + Variant script = function->return_type.script_type; + int script_idx = get_constant_pos(script); + script_idx |= (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); + + append(GDScriptFunction::OPCODE_RETURN_TYPED_ARRAY, 2); + append(p_return_value); + append(script_idx); + append(element_type.kind == GDScriptDataType::BUILTIN ? element_type.builtin_type : Variant::OBJECT); + append(element_type.native_type); + } else { + append(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN, 1); + append(p_return_value); + append(function->return_type.builtin_type); + } + } break; + case GDScriptDataType::NATIVE: { + append(GDScriptFunction::OPCODE_RETURN_TYPED_NATIVE, 2); + append(p_return_value); + int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[function->return_type.native_type]; + Variant nc = GDScriptLanguage::get_singleton()->get_global_array()[class_idx]; + class_idx = get_constant_pos(nc) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); + append(class_idx); + } break; + case GDScriptDataType::GDSCRIPT: + case GDScriptDataType::SCRIPT: { + Variant script = function->return_type.script_type; + int script_idx = get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); + + append(GDScriptFunction::OPCODE_RETURN_TYPED_SCRIPT, 2); + append(p_return_value); + append(script_idx); + } break; + default: { + ERR_PRINT("Compiler bug: unresolved return."); + + // Shouldn't get here, but fail-safe to a regular return; + append(GDScriptFunction::OPCODE_RETURN, 1); + append(p_return_value); + } break; + } + } } void GDScriptByteCodeGenerator::write_assert(const Address &p_test, const Address &p_message) { diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 1e66af269a..b1f3cd5fb3 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -37,6 +37,17 @@ #include "gdscript_utility_functions.h" class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { + struct StackSlot { + Variant::Type type = Variant::NIL; + Vector<int> bytecode_indices; + + StackSlot() = default; + StackSlot(Variant::Type p_type) : + type(p_type) {} + }; + + const static int RESERVED_STACK = 3; // For self, class, and nil. + bool ended = false; GDScriptFunction *function = nullptr; bool debug_stack = false; @@ -47,15 +58,17 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { List<int> stack_identifiers_counts; Map<StringName, int> local_constants; + Vector<StackSlot> locals; + Vector<StackSlot> temporaries; + List<int> used_temporaries; + Map<Variant::Type, List<int>> temporaries_pool; + List<GDScriptFunction::StackDebug> stack_debug; List<Map<StringName, int>> block_identifier_stack; Map<StringName, int> block_identifiers; - int current_stack_size = 0; - int current_temporaries = 0; - int current_locals = 0; + int max_locals = 0; int current_line = 0; - int stack_max = 0; int instr_args_max = 0; int ptrcall_max = 0; @@ -80,6 +93,7 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { Map<Variant::ValidatedUtilityFunction, int> utilities_map; Map<GDScriptUtilityFunctions::FunctionPtr, int> gds_utilities_map; Map<MethodBind *, int> method_bind_map; + Map<GDScriptFunction *, int> lambdas_map; // Lists since these can be nested. List<int> if_jmp_addrs; @@ -102,7 +116,9 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { List<List<int>> match_continues_to_patch; void add_stack_identifier(const StringName &p_id, int p_stackpos) { - current_locals++; + if (locals.size() > max_locals) { + max_locals = locals.size(); + } stack_identifiers[p_id] = p_stackpos; if (debug_stack) { block_identifiers[p_id] = p_stackpos; @@ -116,7 +132,7 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { } void push_stack_identifiers() { - stack_identifiers_counts.push_back(current_locals); + stack_identifiers_counts.push_back(locals.size()); stack_id_stack.push_back(stack_identifiers); if (debug_stack) { Map<StringName, int> block_ids(block_identifiers); @@ -126,17 +142,16 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { } void pop_stack_identifiers() { - current_locals = stack_identifiers_counts.back()->get(); + int current_locals = stack_identifiers_counts.back()->get(); stack_identifiers_counts.pop_back(); stack_identifiers = stack_id_stack.back()->get(); stack_id_stack.pop_back(); #ifdef DEBUG_ENABLED - if (current_temporaries != 0) { - ERR_PRINT("Leaving block with non-zero temporary variables: " + itos(current_temporaries)); + if (!used_temporaries.is_empty()) { + ERR_PRINT("Leaving block with non-zero temporary variables: " + itos(used_temporaries.size())); } #endif - current_stack_size = current_locals; - + locals.resize(current_locals); if (debug_stack) { for (Map<StringName, int>::Element *E = block_identifiers.front(); E; E = E->next()) { GDScriptFunction::StackDebug sd; @@ -163,64 +178,72 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { } int get_constant_pos(const Variant &p_constant) { - if (constant_map.has(p_constant)) + if (constant_map.has(p_constant)) { return constant_map[p_constant]; + } int pos = constant_map.size(); constant_map[p_constant] = pos; return pos; } int get_operation_pos(const Variant::ValidatedOperatorEvaluator p_operation) { - if (operator_func_map.has(p_operation)) + if (operator_func_map.has(p_operation)) { return operator_func_map[p_operation]; + } int pos = operator_func_map.size(); operator_func_map[p_operation] = pos; return pos; } int get_setter_pos(const Variant::ValidatedSetter p_setter) { - if (setters_map.has(p_setter)) + if (setters_map.has(p_setter)) { return setters_map[p_setter]; + } int pos = setters_map.size(); setters_map[p_setter] = pos; return pos; } int get_getter_pos(const Variant::ValidatedGetter p_getter) { - if (getters_map.has(p_getter)) + if (getters_map.has(p_getter)) { return getters_map[p_getter]; + } int pos = getters_map.size(); getters_map[p_getter] = pos; return pos; } int get_keyed_setter_pos(const Variant::ValidatedKeyedSetter p_keyed_setter) { - if (keyed_setters_map.has(p_keyed_setter)) + if (keyed_setters_map.has(p_keyed_setter)) { return keyed_setters_map[p_keyed_setter]; + } int pos = keyed_setters_map.size(); keyed_setters_map[p_keyed_setter] = pos; return pos; } int get_keyed_getter_pos(const Variant::ValidatedKeyedGetter p_keyed_getter) { - if (keyed_getters_map.has(p_keyed_getter)) + if (keyed_getters_map.has(p_keyed_getter)) { return keyed_getters_map[p_keyed_getter]; + } int pos = keyed_getters_map.size(); keyed_getters_map[p_keyed_getter] = pos; return pos; } int get_indexed_setter_pos(const Variant::ValidatedIndexedSetter p_indexed_setter) { - if (indexed_setters_map.has(p_indexed_setter)) + if (indexed_setters_map.has(p_indexed_setter)) { return indexed_setters_map[p_indexed_setter]; + } int pos = indexed_setters_map.size(); indexed_setters_map[p_indexed_setter] = pos; return pos; } int get_indexed_getter_pos(const Variant::ValidatedIndexedGetter p_indexed_getter) { - if (indexed_getters_map.has(p_indexed_getter)) + if (indexed_getters_map.has(p_indexed_getter)) { return indexed_getters_map[p_indexed_getter]; + } int pos = indexed_getters_map.size(); indexed_getters_map[p_indexed_getter] = pos; return pos; @@ -271,45 +294,39 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { return pos; } - void alloc_stack(int p_level) { - if (p_level >= stack_max) - stack_max = p_level + 1; - } - - int increase_stack() { - int top = current_stack_size++; - alloc_stack(current_stack_size); - return top; + int get_lambda_function_pos(GDScriptFunction *p_lambda_function) { + if (lambdas_map.has(p_lambda_function)) { + return lambdas_map[p_lambda_function]; + } + int pos = lambdas_map.size(); + lambdas_map[p_lambda_function] = pos; + return pos; } void alloc_ptrcall(int p_params) { - if (p_params >= ptrcall_max) + if (p_params >= ptrcall_max) { ptrcall_max = p_params; + } } int address_of(const Address &p_address) { switch (p_address.mode) { case Address::SELF: - return GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS; + return GDScriptFunction::ADDR_SELF; case Address::CLASS: - return GDScriptFunction::ADDR_TYPE_CLASS << GDScriptFunction::ADDR_BITS; + return GDScriptFunction::ADDR_CLASS; case Address::MEMBER: return p_address.address | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); - case Address::CLASS_CONSTANT: - return p_address.address | (GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT << GDScriptFunction::ADDR_BITS); - case Address::LOCAL_CONSTANT: case Address::CONSTANT: - return p_address.address | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); + return p_address.address | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); case Address::LOCAL_VARIABLE: - case Address::TEMPORARY: case Address::FUNCTION_PARAMETER: return p_address.address | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - case Address::GLOBAL: - return p_address.address | (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); - case Address::NAMED_GLOBAL: - return p_address.address | (GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL << GDScriptFunction::ADDR_BITS); + case Address::TEMPORARY: + temporaries.write[p_address.address].bytecode_indices.push_back(opcodes.size()); + return -1; case Address::NIL: - return GDScriptFunction::ADDR_TYPE_NIL << GDScriptFunction::ADDR_BITS; + return GDScriptFunction::ADDR_NIL; } return -1; // Unreachable. } @@ -379,6 +396,10 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { opcodes.push_back(get_method_bind_pos(p_method)); } + void append(GDScriptFunction *p_lambda_function) { + opcodes.push_back(get_lambda_function_pos(p_lambda_function)); + } + void patch_jump(int p_address) { opcodes.write[p_address] = opcodes.size(); } @@ -389,7 +410,7 @@ public: virtual uint32_t add_local_constant(const StringName &p_name, const Variant &p_constant) override; virtual uint32_t add_or_get_constant(const Variant &p_constant) override; virtual uint32_t add_or_get_name(const StringName &p_name) override; - virtual uint32_t add_temporary() override; + virtual uint32_t add_temporary(const GDScriptDataType &p_type) override; virtual void pop_temporary() override; virtual void start_parameters() override; @@ -406,6 +427,7 @@ public: #endif virtual void set_initial_line(int p_line) override; + virtual void write_type_adjust(const Address &p_target, Variant::Type p_new_type) override; virtual void write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) override; virtual void write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) override; virtual void write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) override; @@ -428,9 +450,11 @@ public: 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_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; virtual void write_assign_false(const Address &p_target) override; virtual void write_assign_default_parameter(const Address &p_dst, const Address &p_src) override; + virtual void write_store_named_global(const Address &p_dst, const StringName &p_global) override; virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) override; virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override; @@ -438,13 +462,16 @@ public: virtual void write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) override; virtual void write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) override; virtual void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override; + virtual void write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override; virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override; virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override; virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override; virtual void write_call_self_async(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override; virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; + virtual void write_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures) override; virtual void write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) override; virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) override; + virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) override; virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) override; virtual void write_await(const Address &p_target, const Address &p_operand) override; virtual void write_if(const Address &p_condition) override; diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 113d36be98..1a844bf241 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -30,7 +30,7 @@ #include "gdscript_cache.h" -#include "core/os/file_access.h" +#include "core/io/file_access.h" #include "core/templates/vector.h" #include "gdscript.h" #include "gdscript_analyzer.h" @@ -134,7 +134,7 @@ Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptP return ref; } GDScriptParser *parser = memnew(GDScriptParser); - ref.instance(); + ref.instantiate(); ref->parser = parser; ref->path = p_path; singleton->parser_map[p_path] = ref.ptr(); @@ -153,9 +153,9 @@ String GDScriptCache::get_source_code(const String &p_path) { ERR_FAIL_COND_V(err, ""); } - int len = f->get_len(); + uint64_t len = f->get_length(); source_file.resize(len + 1); - int r = f->get_buffer(source_file.ptrw(), len); + uint64_t r = f->get_buffer(source_file.ptrw(), len); f->close(); ERR_FAIL_COND_V(r != len, ""); source_file.write[len] = 0; @@ -180,7 +180,7 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const Stri } Ref<GDScript> script; - script.instance(); + script.instantiate(); script->set_path(p_path, true); script->set_script_path(p_path); script->load_source_code(p_path); diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index d1d2a2abbf..943638d29f 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -31,7 +31,7 @@ #ifndef GDSCRIPT_CACHE_H #define GDSCRIPT_CACHE_H -#include "core/object/reference.h" +#include "core/object/ref_counted.h" #include "core/os/mutex.h" #include "core/templates/hash_map.h" #include "core/templates/set.h" @@ -40,7 +40,7 @@ class GDScriptAnalyzer; class GDScriptParser; -class GDScriptParserRef : public Reference { +class GDScriptParserRef : public RefCounted { public: enum Status { EMPTY, diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index d72bd12033..cac6544f03 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -45,13 +45,9 @@ public: CLASS, MEMBER, CONSTANT, - CLASS_CONSTANT, - LOCAL_CONSTANT, LOCAL_VARIABLE, FUNCTION_PARAMETER, TEMPORARY, - GLOBAL, - NAMED_GLOBAL, NIL, }; AddressMode mode = NIL; @@ -75,7 +71,7 @@ public: virtual uint32_t add_local_constant(const StringName &p_name, const Variant &p_constant) = 0; virtual uint32_t add_or_get_constant(const Variant &p_constant) = 0; virtual uint32_t add_or_get_name(const StringName &p_name) = 0; - virtual uint32_t add_temporary() = 0; + virtual uint32_t add_temporary(const GDScriptDataType &p_type) = 0; virtual void pop_temporary() = 0; virtual void start_parameters() = 0; @@ -84,9 +80,6 @@ public: virtual void start_block() = 0; virtual void end_block() = 0; - // virtual int get_max_stack_level() = 0; - // virtual int get_max_function_arguments() = 0; - virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCMode p_rpc_mode, const GDScriptDataType &p_return_type) = 0; virtual GDScriptFunction *write_end() = 0; @@ -95,9 +88,7 @@ public: #endif virtual void set_initial_line(int p_line) = 0; - // virtual void alloc_stack(int p_level) = 0; // Is this needed? - // virtual void alloc_call(int p_arg_count) = 0; // This might be automatic from other functions. - + virtual void write_type_adjust(const Address &p_target, Variant::Type p_new_type) = 0; virtual void write_unary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand) = 0; virtual void write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) = 0; virtual void write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) = 0; @@ -120,9 +111,11 @@ public: 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_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; virtual void write_assign_false(const Address &p_target) = 0; virtual void write_assign_default_parameter(const Address &dst, const Address &src) = 0; + virtual void write_store_named_global(const Address &p_dst, const StringName &p_global) = 0; virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) = 0; virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; @@ -130,17 +123,19 @@ public: virtual void write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) = 0; virtual void write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) = 0; virtual void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) = 0; + virtual void write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) = 0; virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0; virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0; virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; virtual void write_call_self_async(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; virtual void write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; + virtual void write_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures) = 0; virtual void write_construct(const Address &p_target, Variant::Type p_type, const Vector<Address> &p_arguments) = 0; virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) = 0; + virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) = 0; virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) = 0; virtual void write_await(const Address &p_target, const Address &p_operand) = 0; virtual void write_if(const Address &p_condition) = 0; - // virtual void write_elseif(const Address &p_condition) = 0; This kind of makes things more difficult for no real benefit. virtual void write_else() = 0; virtual void write_endif() = 0; virtual void start_for(const GDScriptDataType &p_iterator_type, const GDScriptDataType &p_list_type) = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 7f873ccd7a..7fa8e9c067 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -137,22 +137,22 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } } } break; + case GDScriptParser::DataType::ENUM: case GDScriptParser::DataType::ENUM_VALUE: result.has_type = true; result.kind = GDScriptDataType::BUILTIN; result.builtin_type = Variant::INT; break; - case GDScriptParser::DataType::ENUM: - result.has_type = true; - result.kind = GDScriptDataType::BUILTIN; - result.builtin_type = Variant::DICTIONARY; - break; case GDScriptParser::DataType::UNRESOLVED: { ERR_PRINT("Parser bug: converting unresolved type."); return GDScriptDataType(); } } + if (p_datatype.has_container_element_type()) { + result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type())); + } + // Only hold strong reference to the script if it's not the owner of the // element qualified with this type, to avoid cyclic references (leaks). if (result.script_type && result.script_type == p_owner) { @@ -262,7 +262,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code GDScriptNativeClass *nc = nullptr; while (scr) { if (scr->constants.has(identifier)) { - return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS_CONSTANT, gen->add_or_get_name(identifier)); // TODO: Get type here. + return codegen.add_constant(scr->constants[identifier]); // TODO: Get type here. } if (scr->native.is_valid()) { nc = scr->native.ptr(); @@ -319,7 +319,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; - return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::GLOBAL, idx); // TODO: Get type. + Variant global = GDScriptLanguage::get_singleton()->get_global_array()[idx]; + return codegen.add_constant(global); // TODO: Get type. } // Try global classes. @@ -347,7 +348,9 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code #ifdef TOOLS_ENABLED if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) { - return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::NAMED_GLOBAL, gen->add_or_get_name(identifier)); // TODO: Get type. + GDScriptCodeGenerator::Address global = codegen.add_temporary(); // TODO: Get type. + gen->write_store_named_global(global, identifier); + return global; } #endif @@ -376,10 +379,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code Vector<GDScriptCodeGenerator::Address> values; // Create the result temporary first since it's the last to be killed. - GDScriptDataType array_type; - array_type.has_type = true; - array_type.kind = GDScriptDataType::BUILTIN; - array_type.builtin_type = Variant::ARRAY; + GDScriptDataType array_type = _gdtype_from_datatype(an->get_datatype()); GDScriptCodeGenerator::Address result = codegen.add_temporary(array_type); for (int i = 0; i < an->elements.size(); i++) { @@ -390,7 +390,11 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code values.push_back(val); } - gen->write_construct_array(result, values); + if (array_type.has_container_element_type()) { + gen->write_construct_typed_array(result, array_type.get_container_element_type(), values); + } else { + gen->write_construct_array(result, values); + } for (int i = 0; i < values.size(); i++) { if (values[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { @@ -423,8 +427,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } break; case GDScriptParser::DictionaryNode::LUA_TABLE: - // Lua-style: key is an identifier interpreted as string. - String key = static_cast<const GDScriptParser::IdentifierNode *>(dn->elements[i].key)->name; + // Lua-style: key is an identifier interpreted as StringName. + StringName key = static_cast<const GDScriptParser::IdentifierNode *>(dn->elements[i].key)->name; element = codegen.add_constant(key); break; } @@ -533,39 +537,44 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(call->callee); if (subscript->is_attribute) { - GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base); - if (r_error) { - return GDScriptCodeGenerator::Address(); - } - if (within_await) { - gen->write_call_async(result, base, call->function_name, arguments); - } else if (base.type.has_type && base.type.kind != GDScriptDataType::BUILTIN) { - // Native method, use faster path. - StringName class_name; - if (base.type.kind == GDScriptDataType::NATIVE) { - class_name = base.type.native_type; - } else { - class_name = base.type.native_type == StringName() ? base.type.script_type->get_instance_base_type() : base.type.native_type; + // May be static built-in method call. + if (!call->is_super && subscript->base->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(subscript->base)->name) < Variant::VARIANT_MAX) { + gen->write_call_builtin_type_static(result, GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(subscript->base)->name), subscript->attribute->name, arguments); + } else { + GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base); + if (r_error) { + return GDScriptCodeGenerator::Address(); } - if (ClassDB::class_exists(class_name) && ClassDB::has_method(class_name, call->function_name)) { - MethodBind *method = ClassDB::get_method(class_name, call->function_name); - if (_have_exact_arguments(method, arguments)) { - // Exact arguments, use ptrcall. - gen->write_call_ptrcall(result, base, method, arguments); + if (within_await) { + gen->write_call_async(result, base, call->function_name, arguments); + } else if (base.type.has_type && base.type.kind != GDScriptDataType::BUILTIN) { + // Native method, use faster path. + StringName class_name; + if (base.type.kind == GDScriptDataType::NATIVE) { + class_name = base.type.native_type; + } else { + class_name = base.type.native_type == StringName() ? base.type.script_type->get_instance_base_type() : base.type.native_type; + } + if (ClassDB::class_exists(class_name) && ClassDB::has_method(class_name, call->function_name)) { + MethodBind *method = ClassDB::get_method(class_name, call->function_name); + if (_have_exact_arguments(method, arguments)) { + // Exact arguments, use ptrcall. + gen->write_call_ptrcall(result, base, method, arguments); + } else { + // Not exact arguments, but still can use method bind call. + gen->write_call_method_bind(result, base, method, arguments); + } } else { - // Not exact arguments, but still can use method bind call. - gen->write_call_method_bind(result, base, method, arguments); + gen->write_call(result, base, call->function_name, arguments); } + } else if (base.type.has_type && base.type.kind == GDScriptDataType::BUILTIN) { + gen->write_call_builtin_type(result, base, base.type.builtin_type, call->function_name, arguments); } else { gen->write_call(result, base, call->function_name, arguments); } - } else if (base.type.has_type && base.type.kind == GDScriptDataType::BUILTIN) { - gen->write_call_builtin_type(result, base, base.type.builtin_type, call->function_name, arguments); - } else { - gen->write_call(result, base, call->function_name, arguments); - } - if (base.mode == GDScriptCodeGenerator::Address::TEMPORARY) { - gen->pop_temporary(); + if (base.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } } } else { _set_error("Cannot call something that isn't a function.", call->callee); @@ -676,9 +685,9 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code name = subscript->attribute->name; named = true; } else { - if (subscript->index->type == GDScriptParser::Node::LITERAL && static_cast<const GDScriptParser::LiteralNode *>(subscript->index)->value.get_type() == Variant::STRING) { + if (subscript->index->is_constant && subscript->index->reduced_value.get_type() == Variant::STRING_NAME) { // Also, somehow, named (speed up anyway). - name = static_cast<const GDScriptParser::LiteralNode *>(subscript->index)->value; + name = subscript->index->reduced_value; named = true; } else { // Regular indexing. @@ -707,7 +716,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::UNARY_OPERATOR: { const GDScriptParser::UnaryOpNode *unary = static_cast<const GDScriptParser::UnaryOpNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(unary->get_datatype())); GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, unary->operand); if (r_error) { @@ -725,7 +734,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::BINARY_OPERATOR: { const GDScriptParser::BinaryOpNode *binary = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(binary->get_datatype())); switch (binary->operation) { case GDScriptParser::BinaryOpNode::OP_LOGIC_AND: { @@ -777,6 +786,10 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code gen->pop_temporary(); } } + + if (operand.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } } break; default: { GDScriptCodeGenerator::Address left_operand = _parse_expression(codegen, r_error, binary->left_operand); @@ -958,6 +971,9 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } else { gen->write_set(prev_base, key, assigned); } + if (key.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { gen->pop_temporary(); } @@ -1068,7 +1084,11 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), setter_function, args); } else { // Just assign. - gen->write_assign(target, op_result); + if (assignment->use_conversion_assign) { + gen->write_assign_with_conversion(target, op_result); + } else { + gen->write_assign(target, op_result); + } } if (op_result.mode == GDScriptCodeGenerator::Address::TEMPORARY) { @@ -1083,6 +1103,34 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } return GDScriptCodeGenerator::Address(); // Assignment does not return a value. } break; + case GDScriptParser::Node::LAMBDA: { + const GDScriptParser::LambdaNode *lambda = static_cast<const GDScriptParser::LambdaNode *>(p_expression); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(lambda->get_datatype())); + + Vector<GDScriptCodeGenerator::Address> captures; + captures.resize(lambda->captures.size()); + for (int i = 0; i < lambda->captures.size(); i++) { + captures.write[i] = _parse_expression(codegen, r_error, lambda->captures[i]); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + } + + GDScriptFunction *function = _parse_function(r_error, codegen.script, codegen.class_node, lambda->function, false, true); + if (r_error) { + return GDScriptCodeGenerator::Address(); + } + + gen->write_lambda(result, function, captures); + + for (int i = 0; i < captures.size(); i++) { + if (captures[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } + + return result; + } break; default: { ERR_FAIL_V_MSG(GDScriptCodeGenerator::Address(), "Bug in bytecode compiler, unexpected node in parse tree while parsing expression."); // Unreachable code. } break; @@ -1176,7 +1224,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c codegen.generator->write_and_left_operand(result_addr); // Check value equality. - codegen.generator->write_binary_operator(result_addr, Variant::OP_EQUAL, p_value_addr, expr_addr); + codegen.generator->write_binary_operator(equality_test_addr, Variant::OP_EQUAL, p_value_addr, expr_addr); codegen.generator->write_and_right_operand(equality_test_addr); // AND both type and value equality. @@ -1511,17 +1559,17 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui codegen.start_block(); // Evaluate the match expression. - GDScriptCodeGenerator::Address value_local = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype())); - GDScriptCodeGenerator::Address value = _parse_expression(codegen, error, match->test); + GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype())); + GDScriptCodeGenerator::Address value_expr = _parse_expression(codegen, error, match->test); if (error) { return error; } // Assign to local. // TODO: This can be improved by passing the target to parse_expression(). - gen->write_assign(value_local, value); + gen->write_assign(value, value_expr); - if (value.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + if (value_expr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } @@ -1733,16 +1781,37 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui const GDScriptParser::VariableNode *lv = static_cast<const GDScriptParser::VariableNode *>(s); // Should be already in stack when the block began. GDScriptCodeGenerator::Address local = codegen.locals[lv->identifier->name]; + GDScriptParser::DataType local_type = lv->get_datatype(); if (lv->initializer != nullptr) { + // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. + if (local_type.is_hard_type() && local_type.builtin_type == Variant::ARRAY) { + if (local_type.has_container_element_type()) { + codegen.generator->write_construct_typed_array(local, _gdtype_from_datatype(local_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + } else { + codegen.generator->write_construct_array(local, Vector<GDScriptCodeGenerator::Address>()); + } + } GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, error, lv->initializer); if (error) { return error; } - gen->write_assign(local, src_address); + if (lv->use_conversion_assign) { + gen->write_assign_with_conversion(local, src_address); + } else { + gen->write_assign(local, src_address); + } if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } + } else if (lv->get_datatype().is_hard_type()) { + // Initialize with default for type. + if (local_type.has_container_element_type()) { + codegen.generator->write_construct_typed_array(local, _gdtype_from_datatype(local_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + } else if (local_type.kind == GDScriptParser::DataType::BUILTIN) { + codegen.generator->write_construct(local, local_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); + } + // The `else` branch is for objects, in such case we leave it as `null`. } } break; case GDScriptParser::Node::CONSTANT: { @@ -1779,8 +1848,8 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui return OK; } -Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready) { - Error error = OK; +GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready, bool p_for_lambda) { + r_error = OK; CodeGen codegen; codegen.generator = memnew(GDScriptByteCodeGenerator); @@ -1797,7 +1866,11 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser return_type.builtin_type = Variant::NIL; if (p_func) { - func_name = p_func->identifier->name; + if (p_func->identifier) { + func_name = p_func->identifier->name; + } else { + func_name = "<anonymous lambda>"; + } is_static = p_func->is_static; rpc_mode = p_func->rpc_mode; return_type = _gdtype_from_datatype(p_func->get_datatype(), p_script); @@ -1828,11 +1901,11 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser } // Parse initializer if applies. - bool is_implicit_initializer = !p_for_ready && !p_func; - bool is_initializer = p_func && String(p_func->identifier->name) == GDScriptLanguage::get_singleton()->strings._init; - bool is_for_ready = p_for_ready || (p_func && String(p_func->identifier->name) == "_ready"); + bool is_implicit_initializer = !p_for_ready && !p_func && !p_for_lambda; + bool is_initializer = p_func && !p_for_lambda && String(p_func->identifier->name) == GDScriptLanguage::get_singleton()->strings._init; + bool is_for_ready = p_for_ready || (p_func && !p_for_lambda && String(p_func->identifier->name) == "_ready"); - if (is_implicit_initializer || is_for_ready) { + if (!p_for_lambda && (is_implicit_initializer || is_for_ready)) { // Initialize class fields. for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type != GDScriptParser::ClassNode::Member::VARIABLE) { @@ -1844,21 +1917,45 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser continue; } + GDScriptParser::DataType field_type = field->get_datatype(); + + GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, _gdtype_from_datatype(field->get_datatype())); if (field->initializer) { // Emit proper line change. codegen.generator->write_newline(field->initializer->start_line); - GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, error, field->initializer, false, true); - if (error) { + // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. + if (field_type.is_hard_type() && field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type()) { + if (field_type.has_container_element_type()) { + codegen.generator->write_construct_typed_array(dst_address, _gdtype_from_datatype(field_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + } else { + codegen.generator->write_construct_array(dst_address, Vector<GDScriptCodeGenerator::Address>()); + } + } + GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, r_error, field->initializer, false, true); + if (r_error) { memdelete(codegen.generator); - return error; + return nullptr; } - GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, _gdtype_from_datatype(field->get_datatype())); - codegen.generator->write_assign(dst_address, src_address); + if (field->use_conversion_assign) { + codegen.generator->write_assign_with_conversion(dst_address, src_address); + } else { + codegen.generator->write_assign(dst_address, src_address); + } if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } + } else if (field->get_datatype().is_hard_type()) { + codegen.generator->write_newline(field->start_line); + + // Initialize with default for type. + if (field_type.has_container_element_type()) { + codegen.generator->write_construct_typed_array(dst_address, _gdtype_from_datatype(field_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + } else if (field_type.kind == GDScriptParser::DataType::BUILTIN) { + codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); + } + // The `else` branch is for objects, in such case we leave it as `null`. } } } @@ -1869,10 +1966,10 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser codegen.generator->start_parameters(); for (int i = p_func->parameters.size() - optional_parameters; i < p_func->parameters.size(); i++) { const GDScriptParser::ParameterNode *parameter = p_func->parameters[i]; - GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, error, parameter->default_value, true); - if (error) { + GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, r_error, parameter->default_value, true); + if (r_error) { memdelete(codegen.generator); - return error; + return nullptr; } GDScriptCodeGenerator::Address dst_addr = codegen.parameters[parameter->identifier->name]; codegen.generator->write_assign_default_parameter(dst_addr, src_addr); @@ -1883,10 +1980,10 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser codegen.generator->end_parameters(); } - Error err = _parse_block(codegen, p_func->body); - if (err) { + r_error = _parse_block(codegen, p_func->body); + if (r_error) { memdelete(codegen.generator); - return err; + return nullptr; } } @@ -1912,6 +2009,10 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser signature += "::" + String(func_name); } + if (p_for_lambda) { + signature += "(lambda)"; + } + codegen.generator->set_signature(signature); } #endif @@ -1919,8 +2020,10 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser if (p_func) { codegen.generator->set_initial_line(p_func->start_line); #ifdef TOOLS_ENABLED - p_script->member_lines[func_name] = p_func->start_line; - p_script->doc_functions[func_name] = p_func->doc_description; + if (!p_for_lambda) { + p_script->member_lines[func_name] = p_func->start_line; + p_script->doc_functions[func_name] = p_func->doc_description; + } #endif } else { codegen.generator->set_initial_line(0); @@ -1949,11 +2052,13 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser #endif } - p_script->member_functions[func_name] = gd_function; + if (!p_for_lambda) { + p_script->member_functions[func_name] = gd_function; + } memdelete(codegen.generator); - return OK; + return gd_function; } Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter) { @@ -1972,6 +2077,8 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP func_name = "@" + p_variable->identifier->name + "_getter"; } + codegen.function_name = func_name; + GDScriptDataType return_type; if (p_is_setter) { return_type.has_type = true; @@ -2116,7 +2223,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar if (err) { return err; } - if (base.is_null() && !base->is_valid()) { + if (base.is_null() || !base->is_valid()) { return ERR_COMPILATION_FAILED; } } @@ -2175,7 +2282,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar } prop_info.hint = export_info.hint; prop_info.hint_string = export_info.hint_string; - prop_info.usage = export_info.usage; + prop_info.usage = export_info.usage | PROPERTY_USAGE_SCRIPT_VARIABLE; } else { prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE; } @@ -2345,7 +2452,8 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa if (!has_ready && function->identifier->name == "_ready") { has_ready = true; } - Error err = _parse_function(p_script, p_class, function); + Error err = OK; + _parse_function(err, p_script, p_class, function); if (err) { return err; } @@ -2370,7 +2478,8 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa { // Create an implicit constructor in any case. - Error err = _parse_function(p_script, p_class, nullptr); + Error err = OK; + _parse_function(err, p_script, p_class, nullptr); if (err) { return err; } @@ -2378,7 +2487,8 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa if (!has_ready && p_class->onready_used) { //create a _ready constructor - Error err = _parse_function(p_script, p_class, nullptr, true); + Error err = OK; + _parse_function(err, p_script, p_class, nullptr, true); if (err) { return err; } @@ -2402,7 +2512,7 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa p_script->placeholders.erase(psi); //remove placeholder GDScriptInstance *instance = memnew(GDScriptInstance); - instance->base_ref = Object::cast_to<Reference>(E->get()); + instance->base_ref_counted = Object::cast_to<RefCounted>(E->get()); instance->members.resize(p_script->member_indices.size()); instance->script = Ref<GDScript>(p_script); instance->owner = E->get(); @@ -2419,7 +2529,7 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa p_script->initializer->call(instance, nullptr, 0, ce); if (ce.error != Callable::CallError::CALL_OK) { - //well, tough luck, not goinna do anything here + //well, tough luck, not gonna do anything here } } #endif @@ -2477,7 +2587,7 @@ void GDScriptCompiler::_make_scripts(GDScript *p_script, const GDScriptParser::C if (orphan_subclass.is_valid()) { subclass = orphan_subclass; } else { - subclass.instance(); + subclass.instantiate(); } } diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 651391f972..7d5bee93ac 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -61,12 +61,12 @@ class GDScriptCompiler { GDScriptCodeGenerator::Address add_local_constant(const StringName &p_name, const Variant &p_value) { uint32_t addr = generator->add_local_constant(p_name, p_value); - locals[p_name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::LOCAL_CONSTANT, addr); + locals[p_name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CONSTANT, addr); return locals[p_name]; } GDScriptCodeGenerator::Address add_temporary(const GDScriptDataType &p_type = GDScriptDataType()) { - uint32_t addr = generator->add_temporary(); + uint32_t addr = generator->add_temporary(p_type); return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::TEMPORARY, addr, p_type); } @@ -128,7 +128,7 @@ class GDScriptCompiler { GDScriptCodeGenerator::Address _parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested); void _add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block); Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true); - Error _parse_function(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false); + GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = false); Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter); Error _parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); Error _parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index 17cb5e3c96..1acb9ceddc 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -69,35 +69,23 @@ static String _disassemble_address(const GDScript *p_script, const GDScriptFunct int addr = p_address & GDScriptFunction::ADDR_MASK; switch (p_address >> GDScriptFunction::ADDR_BITS) { - case GDScriptFunction::ADDR_TYPE_SELF: { - return "self"; - } break; - case GDScriptFunction::ADDR_TYPE_CLASS: { - return "class"; - } break; case GDScriptFunction::ADDR_TYPE_MEMBER: { return "member(" + p_script->debug_get_member_by_index(addr) + ")"; } break; - case GDScriptFunction::ADDR_TYPE_CLASS_CONSTANT: { - return "class_const(" + p_function.get_global_name(addr) + ")"; - } break; - case GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT: { + case GDScriptFunction::ADDR_TYPE_CONSTANT: { return "const(" + _get_variant_string(p_function.get_constant(addr)) + ")"; } break; case GDScriptFunction::ADDR_TYPE_STACK: { - return "stack(" + itos(addr) + ")"; - } break; - case GDScriptFunction::ADDR_TYPE_STACK_VARIABLE: { - return "var_stack(" + itos(addr) + ")"; - } break; - case GDScriptFunction::ADDR_TYPE_GLOBAL: { - return "global(" + _get_variant_string(GDScriptLanguage::get_singleton()->get_global_array()[addr]) + ")"; - } break; - case GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL: { - return "named_global(" + p_function.get_global_name(addr) + ")"; - } break; - case GDScriptFunction::ADDR_TYPE_NIL: { - return "nil"; + switch (addr) { + case GDScriptFunction::ADDR_STACK_SELF: + return "self"; + case GDScriptFunction::ADDR_STACK_CLASS: + return "class"; + case GDScriptFunction::ADDR_STACK_NIL: + return "nil"; + default: + return "stack(" + itos(addr) + ")"; + } } break; } @@ -322,6 +310,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 4; } break; + case OPCODE_ASSIGN_TYPED_ARRAY: { + text += "assign typed array "; + text += DADDR(1); + text += " = "; + text += DADDR(2); + + incr += 3; + } break; case OPCODE_ASSIGN_TYPED_NATIVE: { text += "assign typed native ("; text += DADDR(3); @@ -385,8 +381,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += Variant::get_type_name(t) + "("; for (int i = 0; i < argc; i++) { - if (i > 0) + if (i > 0) { text += ", "; + } text += DADDR(i + 1); } text += ")"; @@ -400,10 +397,11 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += DADDR(1 + argc); text += " = "; - text += "<unkown type>("; + text += "<unknown type>("; for (int i = 0; i < argc; i++) { - if (i > 0) + if (i > 0) { text += ", "; + } text += DADDR(i + 1); } text += ")"; @@ -417,8 +415,43 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += " = ["; for (int i = 0; i < argc; i++) { - if (i > 0) + if (i > 0) { + text += ", "; + } + text += DADDR(1 + i); + } + + text += "]"; + + incr += 3 + argc; + } break; + case OPCODE_CONSTRUCT_TYPED_ARRAY: { + int argc = _code_ptr[ip + 1 + instr_var_args]; + + Ref<Script> script_type = get_constant(_code_ptr[ip + argc + 2]); + Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + argc + 4]; + StringName native_type = get_global_name(_code_ptr[ip + argc + 5]); + + String type_name; + if (script_type.is_valid() && script_type->is_valid()) { + type_name = script_type->get_path(); + } else if (native_type != StringName()) { + type_name = native_type; + } else { + type_name = Variant::get_type_name(builtin_type); + } + + text += " make_typed_array ("; + text += type_name; + text += ") "; + + text += DADDR(1 + argc); + text += " = ["; + + for (int i = 0; i < argc; i++) { + if (i > 0) { text += ", "; + } text += DADDR(1 + i); } @@ -433,8 +466,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += " = {"; for (int i = 0; i < argc; i++) { - if (i > 0) + if (i > 0) { text += ", "; + } text += DADDR(1 + i * 2 + 0); text += ": "; text += DADDR(1 + i * 2 + 1); @@ -468,8 +502,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += "("; for (int i = 0; i < argc; i++) { - if (i > 0) + if (i > 0) { text += ", "; + } text += DADDR(1 + i); } text += ")"; @@ -498,14 +533,37 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += "("; for (int i = 0; i < argc; i++) { - if (i > 0) + if (i > 0) { text += ", "; + } text += DADDR(1 + i); } text += ")"; incr = 5 + argc; } break; + case OPCODE_CALL_BUILTIN_STATIC: { + Variant::Type type = (Variant::Type)_code_ptr[ip + 1 + instr_var_args]; + int argc = _code_ptr[ip + 3 + instr_var_args]; + + text += "call built-in method static "; + text += DADDR(1 + argc); + text += " = "; + text += Variant::get_type_name(type); + text += "."; + text += _global_names_ptr[_code_ptr[ip + 2 + instr_var_args]].operator String(); + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) { + text += ", "; + } + text += DADDR(1 + i); + } + text += ")"; + + incr += 5 + argc; + } break; case OPCODE_CALL_PTRCALL_NO_RETURN: { text += "call-ptrcall (no return) "; @@ -518,8 +576,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += "("; for (int i = 0; i < argc; i++) { - if (i > 0) + if (i > 0) { text += ", "; + } text += DADDR(1 + i); } text += ")"; @@ -561,12 +620,12 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { DISASSEMBLE_PTRCALL(PLANE); DISASSEMBLE_PTRCALL(AABB); DISASSEMBLE_PTRCALL(BASIS); - DISASSEMBLE_PTRCALL(TRANSFORM); + DISASSEMBLE_PTRCALL(TRANSFORM3D); DISASSEMBLE_PTRCALL(COLOR); DISASSEMBLE_PTRCALL(STRING_NAME); DISASSEMBLE_PTRCALL(NODE_PATH); DISASSEMBLE_PTRCALL(RID); - DISASSEMBLE_PTRCALL(QUAT); + DISASSEMBLE_PTRCALL(QUATERNION); DISASSEMBLE_PTRCALL(OBJECT); DISASSEMBLE_PTRCALL(CALLABLE); DISASSEMBLE_PTRCALL(SIGNAL); @@ -595,8 +654,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += "("; for (int i = 0; i < argc; i++) { - if (i > 0) + if (i > 0) { text += ", "; + } text += DADDR(1 + i); } text += ")"; @@ -613,8 +673,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += "("; for (int i = 0; i < argc; i++) { - if (i > 0) + if (i > 0) { text += ", "; + } text += DADDR(1 + i); } text += ")"; @@ -627,12 +688,13 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { int argc = _code_ptr[ip + 1 + instr_var_args]; text += DADDR(1 + argc) + " = "; - text += "<unkown function>"; + text += "<unknown function>"; text += "("; for (int i = 0; i < argc; i++) { - if (i > 0) + if (i > 0) { text += ", "; + } text += DADDR(1 + i); } text += ")"; @@ -649,8 +711,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += "("; for (int i = 0; i < argc; i++) { - if (i > 0) + if (i > 0) { text += ", "; + } text += DADDR(1 + i); } text += ")"; @@ -667,8 +730,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += "("; for (int i = 0; i < argc; i++) { - if (i > 0) + if (i > 0) { text += ", "; + } text += DADDR(1 + i); } text += ")"; @@ -679,7 +743,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += "await "; text += DADDR(1); - incr += 2; + incr = 2; } break; case OPCODE_AWAIT_RESUME: { text += "await resume "; @@ -687,6 +751,25 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr = 2; } break; + case OPCODE_CREATE_LAMBDA: { + int captures_count = _code_ptr[ip + 1 + instr_var_args]; + GDScriptFunction *lambda = _lambdas_ptr[_code_ptr[ip + 2 + instr_var_args]]; + + text += DADDR(1 + captures_count); + text += "create lambda from "; + text += lambda->name.operator String(); + text += "function, captures ("; + + for (int i = 0; i < captures_count; i++) { + if (i > 0) { + text += ", "; + } + text += DADDR(1 + i); + } + text += ")"; + + incr = 3 + captures_count; + } break; case OPCODE_JUMP: { text += "jump "; text += itos(_code_ptr[ip + 1]); @@ -720,6 +803,39 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr = 2; } break; + case OPCODE_RETURN_TYPED_BUILTIN: { + text += "return typed builtin ("; + text += Variant::get_type_name((Variant::Type)_code_ptr[ip + 2]); + text += ") "; + text += DADDR(1); + + incr += 3; + } break; + case OPCODE_RETURN_TYPED_ARRAY: { + text += "return typed array "; + text += DADDR(1); + + incr += 5; + } break; + case OPCODE_RETURN_TYPED_NATIVE: { + text += "return typed native ("; + text += DADDR(2); + text += ") "; + text += DADDR(1); + + incr += 3; + } break; + case OPCODE_RETURN_TYPED_SCRIPT: { + Variant script = _constants_ptr[_code_ptr[ip + 2]]; + Script *sc = Object::cast_to<Script>(script.operator Object *()); + + text += "return typed script ("; + text += sc->get_path(); + text += ") "; + text += DADDR(1); + + incr += 3; + } break; #define DISASSEMBLE_ITERATE(m_type) \ case OPCODE_ITERATE_##m_type: { \ @@ -798,6 +914,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 5; } break; DISASSEMBLE_ITERATE_TYPES(DISASSEMBLE_ITERATE); + case OPCODE_STORE_NAMED_GLOBAL: { + text += "store named global "; + text += DADDR(1); + text += " = "; + text += String(_global_names_ptr[_code_ptr[ip + 2]]); + + incr += 3; + } break; case OPCODE_LINE: { int line = _code_ptr[ip + 1] - 1; if (line >= 0 && line < p_code_lines.size()) { @@ -811,6 +935,51 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 2; } break; + +#define DISASSEMBLE_TYPE_ADJUST(m_v_type) \ + case OPCODE_TYPE_ADJUST_##m_v_type: { \ + text += "type adjust ("; \ + text += #m_v_type; \ + text += ") "; \ + text += DADDR(1); \ + incr += 2; \ + } break + + DISASSEMBLE_TYPE_ADJUST(BOOL); + DISASSEMBLE_TYPE_ADJUST(INT); + DISASSEMBLE_TYPE_ADJUST(FLOAT); + DISASSEMBLE_TYPE_ADJUST(STRING); + DISASSEMBLE_TYPE_ADJUST(VECTOR2); + DISASSEMBLE_TYPE_ADJUST(VECTOR2I); + DISASSEMBLE_TYPE_ADJUST(RECT2); + DISASSEMBLE_TYPE_ADJUST(RECT2I); + DISASSEMBLE_TYPE_ADJUST(VECTOR3); + DISASSEMBLE_TYPE_ADJUST(VECTOR3I); + DISASSEMBLE_TYPE_ADJUST(TRANSFORM2D); + DISASSEMBLE_TYPE_ADJUST(PLANE); + DISASSEMBLE_TYPE_ADJUST(QUATERNION); + DISASSEMBLE_TYPE_ADJUST(AABB); + DISASSEMBLE_TYPE_ADJUST(BASIS); + DISASSEMBLE_TYPE_ADJUST(TRANSFORM); + DISASSEMBLE_TYPE_ADJUST(COLOR); + DISASSEMBLE_TYPE_ADJUST(STRING_NAME); + DISASSEMBLE_TYPE_ADJUST(NODE_PATH); + DISASSEMBLE_TYPE_ADJUST(RID); + DISASSEMBLE_TYPE_ADJUST(OBJECT); + DISASSEMBLE_TYPE_ADJUST(CALLABLE); + DISASSEMBLE_TYPE_ADJUST(SIGNAL); + DISASSEMBLE_TYPE_ADJUST(DICTIONARY); + DISASSEMBLE_TYPE_ADJUST(ARRAY); + DISASSEMBLE_TYPE_ADJUST(PACKED_BYTE_ARRAY); + DISASSEMBLE_TYPE_ADJUST(PACKED_INT32_ARRAY); + DISASSEMBLE_TYPE_ADJUST(PACKED_INT64_ARRAY); + DISASSEMBLE_TYPE_ADJUST(PACKED_FLOAT32_ARRAY); + DISASSEMBLE_TYPE_ADJUST(PACKED_FLOAT64_ARRAY); + DISASSEMBLE_TYPE_ADJUST(PACKED_STRING_ARRAY); + DISASSEMBLE_TYPE_ADJUST(PACKED_VECTOR2_ARRAY); + DISASSEMBLE_TYPE_ADJUST(PACKED_VECTOR3_ARRAY); + DISASSEMBLE_TYPE_ADJUST(PACKED_COLOR_ARRAY); + case OPCODE_ASSERT: { text += "assert ("; text += DADDR(1); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index b17971cf93..c1a7bedbc8 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -32,7 +32,7 @@ #include "core/config/engine.h" #include "core/core_constants.h" -#include "core/os/file_access.h" +#include "core/io/file_access.h" #include "gdscript_analyzer.h" #include "gdscript_compiler.h" #include "gdscript_parser.h" @@ -104,7 +104,7 @@ Ref<Script> GDScriptLanguage::get_template(const String &p_class_name, const Str _template = _get_processed_template(_template, p_base_class_name); Ref<GDScript> script; - script.instance(); + script.instantiate(); script->set_source_code(_template); return script; @@ -131,7 +131,7 @@ static void get_function_names_recursively(const GDScriptParser::ClassNode *p_cl } } -bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, List<ScriptLanguage::Warning> *r_warnings, Set<int> *r_safe_lines) const { +bool GDScriptLanguage::validate(const String &p_script, const String &p_path, List<String> *r_functions, List<ScriptLanguage::ScriptError> *r_errors, List<ScriptLanguage::Warning> *r_warnings, Set<int> *r_safe_lines) const { GDScriptParser parser; GDScriptAnalyzer analyzer(&parser); @@ -156,10 +156,16 @@ bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int & } #endif if (err) { - GDScriptParser::ParserError parse_error = parser.get_errors().front()->get(); - r_line_error = parse_error.line; - r_col_error = parse_error.column; - r_test_error = parse_error.message; + if (r_errors) { + for (const List<GDScriptParser::ParserError>::Element *E = parser.get_errors().front(); E; E = E->next()) { + const GDScriptParser::ParserError &pe = E->get(); + ScriptLanguage::ScriptError e; + e.line = pe.line; + e.column = pe.column; + e.message = pe.message; + r_errors->push_back(e); + } + } return false; } else { const GDScriptParser::ClassNode *cl = parser.get_tree(); @@ -500,36 +506,6 @@ struct GDScriptCompletionIdentifier { const GDScriptParser::ExpressionNode *assigned_expression = nullptr; }; -// TODO: Move this to a central location (maybe core?). -static const char *underscore_classes[] = { - "ClassDB", - "Directory", - "Engine", - "File", - "Geometry", - "GodotSharp", - "JSON", - "Marshalls", - "Mutex", - "OS", - "ResourceLoader", - "ResourceSaver", - "Semaphore", - "Thread", - "VisualScriptEditor", - nullptr, -}; -static StringName _get_real_class_name(const StringName &p_source) { - const char **class_name = underscore_classes; - while (*class_name != nullptr) { - if (p_source == *class_name) { - return String("_") + p_source; - } - class_name++; - } - return p_source; -} - static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg = true) { if (p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { String enum_name = p_info.class_name; @@ -768,7 +744,13 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, Map<String, ScriptCodeCompletionOption> &r_result) { for (int i = 0; i < p_suite->locals.size(); i++) { - ScriptCodeCompletionOption option(p_suite->locals[i].name, ScriptCodeCompletionOption::KIND_VARIABLE); + ScriptCodeCompletionOption option; + if (p_suite->locals[i].type == GDScriptParser::SuiteNode::Local::CONSTANT) { + option = ScriptCodeCompletionOption(p_suite->locals[i].name, ScriptCodeCompletionOption::KIND_CONSTANT); + option.default_value = p_suite->locals[i].constant->initializer->reduced_value; + } else { + option = ScriptCodeCompletionOption(p_suite->locals[i].name, ScriptCodeCompletionOption::KIND_VARIABLE); + } r_result.insert(option.display, option); } if (p_suite->parent_block) { @@ -930,7 +912,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base } } break; case GDScriptParser::DataType::NATIVE: { - StringName type = _get_real_class_name(base_type.native_type); + StringName type = GDScriptParser::get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(type)) { return; } @@ -1053,7 +1035,7 @@ static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool } static const char *_type_names[Variant::VARIANT_MAX] = { - "null", "bool", "int", "float", "String", "StringName", "Vector2", "Vector2i", "Rect2", "Rect2i", "Vector3", "Vector3i", "Transform2D", "Plane", "Quat", "AABB", "Basis", "Transform", + "null", "bool", "int", "float", "String", "StringName", "Vector2", "Vector2i", "Rect2", "Rect2i", "Vector3", "Vector3i", "Transform2D", "Plane", "Quaternion", "AABB", "Basis", "Transform3D", "Color", "NodePath", "RID", "Signal", "Callable", "Object", "Dictionary", "Array", "PackedByteArray", "PackedInt32Array", "PackedInt64Array", "PackedFloat32Array", "PackedFloat64Array", "PackedStringArray", "PackedVector2Array", "PackedVector3Array", "PackedColorArray" }; @@ -1067,7 +1049,7 @@ static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool static const char *_keywords[] = { "false", "PI", "TAU", "INF", "NAN", "self", "true", "breakpoint", "tool", "super", "break", "continue", "pass", "return", - 0 + nullptr }; const char **kw = _keywords; @@ -1080,7 +1062,7 @@ static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool static const char *_keywords_with_space[] = { "and", "in", "not", "or", "as", "class", "extends", "is", "func", "signal", "await", "const", "enum", "static", "var", "if", "elif", "else", "for", "match", "while", - 0 + nullptr }; const char **kws = _keywords_with_space; @@ -1093,7 +1075,7 @@ static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool static const char *_keywords_with_args[] = { "assert", "preload", - 0 + nullptr }; const char **kwa = _keywords_with_args; @@ -1775,15 +1757,15 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, return true; } } - base_type = base_type.class_type->base_type; } + base_type = base_type.class_type->base_type; break; case GDScriptParser::DataType::NATIVE: { if (id_type.is_set() && !id_type.is_variant()) { base_type = GDScriptParser::DataType(); break; } - StringName real_native = _get_real_class_name(base_type.native_type); + StringName real_native = GDScriptParser::get_real_class_name(base_type.native_type); MethodInfo info; if (ClassDB::get_method_info(real_native, p_context.current_function->identifier->name, &info)) { for (const List<PropertyInfo>::Element *E = info.arguments.front(); E; E = E->next()) { @@ -1854,7 +1836,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } // Check ClassDB. - StringName class_name = _get_real_class_name(p_identifier); + StringName class_name = GDScriptParser::get_real_class_name(p_identifier); if (ClassDB::class_exists(class_name) && ClassDB::is_class_exposed(class_name)) { r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.kind = GDScriptParser::DataType::NATIVE; @@ -1970,7 +1952,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & } } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = _get_real_class_name(base_type.native_type); + StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(class_name)) { return false; } @@ -2133,7 +2115,7 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex } } break; case GDScriptParser::DataType::NATIVE: { - StringName native = _get_real_class_name(base_type.native_type); + StringName native = GDScriptParser::get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(native)) { return false; } @@ -2230,7 +2212,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c base_type = base_type.class_type->base_type; } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = _get_real_class_name(base_type.native_type); + StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(class_name)) { base_type.kind = GDScriptParser::DataType::UNRESOLVED; break; @@ -2261,10 +2243,9 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } r_arghint = _make_arguments_hint(info, p_argidx); - return; } - if (ClassDB::is_parent_class(class_name, "Node") && (p_method == "get_node" || p_method == "has_node") && p_argidx == 0) { + if (p_argidx == 0 && ClassDB::is_parent_class(class_name, "Node") && (p_method == "get_node" || p_method == "has_node")) { // Get autoloads List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); @@ -2403,7 +2384,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c r_forced = r_result.size() > 0; } -Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) { +::Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) { const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; GDScriptParser parser; @@ -2616,7 +2597,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path break; } - StringName class_name = _get_real_class_name(native_type.native_type); + StringName class_name = GDScriptParser::get_real_class_name(native_type.native_type); if (!ClassDB::class_exists(class_name)) { break; } @@ -2845,7 +2826,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = _get_real_class_name(base_type.native_type); + StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type); if (!ClassDB::class_exists(class_name)) { base_type.kind = GDScriptParser::DataType::UNRESOLVED; break; @@ -2898,7 +2879,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co StringName parent = ClassDB::get_parent_class(class_name); if (parent != StringName()) { if (String(parent).begins_with("_")) { - base_type.native_type = String(parent).right(1); + base_type.native_type = String(parent).substr(1); } else { base_type.native_type = parent; } @@ -2919,11 +2900,11 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co Variant v; REF v_ref; if (base_type.builtin_type == Variant::OBJECT) { - v_ref.instance(); + v_ref.instantiate(); v = v_ref; } else { Callable::CallError err; - Variant::construct(base_type.builtin_type, v, NULL, 0, err); + Variant::construct(base_type.builtin_type, v, nullptr, 0, err); if (err.error != Callable::CallError::CALL_OK) { break; } @@ -2954,7 +2935,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co return ERR_CANT_RESOLVE; } -Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) { +::Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) { //before parsing, try the usual stuff if (ClassDB::class_exists(p_symbol)) { r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS; @@ -3024,6 +3005,7 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol is_function = true; [[fallthrough]]; } + case GDScriptParser::COMPLETION_CALL_ARGUMENTS: case GDScriptParser::COMPLETION_IDENTIFIER: { GDScriptParser::DataType base_type; if (context.current_class) { @@ -3056,9 +3038,9 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol if (!is_function) { // Guess in autoloads as singletons. if (ProjectSettings::get_singleton()->has_autoload(p_symbol)) { - const ProjectSettings::AutoloadInfo &singleton = ProjectSettings::get_singleton()->get_autoload(p_symbol); - if (singleton.is_singleton) { - String script = singleton.path; + const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(p_symbol); + if (autoload.is_singleton) { + String script = autoload.path; if (!script.ends_with(".gd")) { // Not a script, try find the script anyway, // may have some success. @@ -3091,7 +3073,7 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol // proxy class remove the underscore. if (r_result.class_name.begins_with("_")) { - r_result.class_name = r_result.class_name.right(1); + r_result.class_name = r_result.class_name.substr(1); } return OK; } @@ -3101,7 +3083,7 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol // We cannot determine the exact nature of the identifier here // Otherwise these codes would work StringName enumName = ClassDB::get_integer_constant_enum("@GlobalScope", p_symbol, true); - if (enumName != NULL) { + if (enumName != nullptr) { r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_ENUM; r_result.class_name = "@GlobalScope"; r_result.class_member = enumName; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index c6c9a439df..78399114a5 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -150,6 +150,10 @@ GDScriptFunction::GDScriptFunction() { } GDScriptFunction::~GDScriptFunction() { + for (int i = 0; i < lambdas.size(); i++) { + memdelete(lambdas[i]); + } + #ifdef DEBUG_ENABLED MutexLock lock(GDScriptLanguage::get_singleton()->lock); @@ -244,7 +248,7 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) { bool completed = true; // If the return value is a GDScriptFunctionState reference, - // then the function did awaited again after resuming. + // then the function did await again after resuming. if (ret.is_ref()) { GDScriptFunctionState *gdfs = Object::cast_to<GDScriptFunctionState>(ret); if (gdfs && gdfs->function == function) { diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index e64630a743..553c2ecc01 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -31,7 +31,7 @@ #ifndef GDSCRIPT_FUNCTION_H #define GDSCRIPT_FUNCTION_H -#include "core/object/reference.h" +#include "core/object/ref_counted.h" #include "core/object/script_language.h" #include "core/os/thread.h" #include "core/string/string_name.h" @@ -43,7 +43,11 @@ class GDScriptInstance; class GDScript; -struct GDScriptDataType { +class GDScriptDataType { +private: + GDScriptDataType *container_element_type = nullptr; + +public: enum Kind { UNINITIALIZED, BUILTIN, @@ -71,7 +75,24 @@ struct GDScriptDataType { case BUILTIN: { Variant::Type var_type = p_variant.get_type(); bool valid = builtin_type == var_type; - if (!valid && p_allow_implicit_conversion) { + if (valid && builtin_type == Variant::ARRAY && has_container_element_type()) { + Array array = p_variant; + if (array.is_typed()) { + Variant::Type array_builtin_type = (Variant::Type)array.get_typed_builtin(); + StringName array_native_type = array.get_typed_class_name(); + Ref<Script> array_script_type_ref = array.get_typed_script(); + + if (array_script_type_ref.is_valid()) { + valid = (container_element_type->kind == SCRIPT || container_element_type->kind == GDSCRIPT) && container_element_type->script_type == array_script_type_ref.ptr(); + } else if (array_native_type != StringName()) { + valid = container_element_type->kind == NATIVE && container_element_type->native_type == array_native_type; + } else { + valid = container_element_type->kind == BUILTIN && container_element_type->builtin_type == array_builtin_type; + } + } else { + valid = false; + } + } else if (!valid && p_allow_implicit_conversion) { valid = Variant::can_convert_strict(var_type, builtin_type); } return valid; @@ -153,7 +174,49 @@ struct GDScriptDataType { return info; } - GDScriptDataType() {} + void set_container_element_type(const GDScriptDataType &p_element_type) { + container_element_type = memnew(GDScriptDataType(p_element_type)); + } + + GDScriptDataType get_container_element_type() const { + ERR_FAIL_COND_V(container_element_type == nullptr, GDScriptDataType()); + return *container_element_type; + } + + bool has_container_element_type() const { + return container_element_type != nullptr; + } + + void unset_container_element_type() { + if (container_element_type) { + memdelete(container_element_type); + } + container_element_type = nullptr; + } + + GDScriptDataType() = default; + + GDScriptDataType &operator=(const GDScriptDataType &p_other) { + kind = p_other.kind; + has_type = p_other.has_type; + builtin_type = p_other.builtin_type; + native_type = p_other.native_type; + script_type = p_other.script_type; + script_type_ref = p_other.script_type_ref; + unset_container_element_type(); + if (p_other.has_container_element_type()) { + set_container_element_type(p_other.get_container_element_type()); + } + return *this; + } + + GDScriptDataType(const GDScriptDataType &p_other) { + *this = p_other; + } + + ~GDScriptDataType() { + unset_container_element_type(); + } }; class GDScriptFunction { @@ -179,6 +242,7 @@ public: OPCODE_ASSIGN_TRUE, OPCODE_ASSIGN_FALSE, OPCODE_ASSIGN_TYPED_BUILTIN, + OPCODE_ASSIGN_TYPED_ARRAY, OPCODE_ASSIGN_TYPED_NATIVE, OPCODE_ASSIGN_TYPED_SCRIPT, OPCODE_CAST_TO_BUILTIN, @@ -187,6 +251,7 @@ public: OPCODE_CONSTRUCT, // Only for basic types! OPCODE_CONSTRUCT_VALIDATED, // Only for basic types! OPCODE_CONSTRUCT_ARRAY, + OPCODE_CONSTRUCT_TYPED_ARRAY, OPCODE_CONSTRUCT_DICTIONARY, OPCODE_CALL, OPCODE_CALL_RETURN, @@ -198,6 +263,7 @@ public: OPCODE_CALL_SELF_BASE, OPCODE_CALL_METHOD_BIND, OPCODE_CALL_METHOD_BIND_RET, + OPCODE_CALL_BUILTIN_STATIC, // ptrcall have one instruction per return type. OPCODE_CALL_PTRCALL_NO_RETURN, OPCODE_CALL_PTRCALL_BOOL, @@ -212,10 +278,10 @@ public: OPCODE_CALL_PTRCALL_VECTOR3I, OPCODE_CALL_PTRCALL_TRANSFORM2D, OPCODE_CALL_PTRCALL_PLANE, - OPCODE_CALL_PTRCALL_QUAT, + OPCODE_CALL_PTRCALL_QUATERNION, OPCODE_CALL_PTRCALL_AABB, OPCODE_CALL_PTRCALL_BASIS, - OPCODE_CALL_PTRCALL_TRANSFORM, + OPCODE_CALL_PTRCALL_TRANSFORM3D, OPCODE_CALL_PTRCALL_COLOR, OPCODE_CALL_PTRCALL_STRING_NAME, OPCODE_CALL_PTRCALL_NODE_PATH, @@ -236,11 +302,16 @@ public: OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY, OPCODE_AWAIT, OPCODE_AWAIT_RESUME, + OPCODE_CREATE_LAMBDA, OPCODE_JUMP, OPCODE_JUMP_IF, OPCODE_JUMP_IF_NOT, OPCODE_JUMP_TO_DEF_ARGUMENT, OPCODE_RETURN, + OPCODE_RETURN_TYPED_BUILTIN, + OPCODE_RETURN_TYPED_ARRAY, + OPCODE_RETURN_TYPED_NATIVE, + OPCODE_RETURN_TYPED_SCRIPT, OPCODE_ITERATE_BEGIN, OPCODE_ITERATE_BEGIN_INT, OPCODE_ITERATE_BEGIN_FLOAT, @@ -281,6 +352,41 @@ public: OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, OPCODE_ITERATE_PACKED_COLOR_ARRAY, OPCODE_ITERATE_OBJECT, + OPCODE_STORE_NAMED_GLOBAL, + OPCODE_TYPE_ADJUST_BOOL, + OPCODE_TYPE_ADJUST_INT, + OPCODE_TYPE_ADJUST_FLOAT, + OPCODE_TYPE_ADJUST_STRING, + OPCODE_TYPE_ADJUST_VECTOR2, + OPCODE_TYPE_ADJUST_VECTOR2I, + OPCODE_TYPE_ADJUST_RECT2, + OPCODE_TYPE_ADJUST_RECT2I, + OPCODE_TYPE_ADJUST_VECTOR3, + OPCODE_TYPE_ADJUST_VECTOR3I, + OPCODE_TYPE_ADJUST_TRANSFORM2D, + OPCODE_TYPE_ADJUST_PLANE, + OPCODE_TYPE_ADJUST_QUATERNION, + OPCODE_TYPE_ADJUST_AABB, + OPCODE_TYPE_ADJUST_BASIS, + OPCODE_TYPE_ADJUST_TRANSFORM, + OPCODE_TYPE_ADJUST_COLOR, + OPCODE_TYPE_ADJUST_STRING_NAME, + OPCODE_TYPE_ADJUST_NODE_PATH, + OPCODE_TYPE_ADJUST_RID, + OPCODE_TYPE_ADJUST_OBJECT, + OPCODE_TYPE_ADJUST_CALLABLE, + OPCODE_TYPE_ADJUST_SIGNAL, + OPCODE_TYPE_ADJUST_DICTIONARY, + OPCODE_TYPE_ADJUST_ARRAY, + OPCODE_TYPE_ADJUST_PACKED_BYTE_ARRAY, + OPCODE_TYPE_ADJUST_PACKED_INT32_ARRAY, + OPCODE_TYPE_ADJUST_PACKED_INT64_ARRAY, + OPCODE_TYPE_ADJUST_PACKED_FLOAT32_ARRAY, + OPCODE_TYPE_ADJUST_PACKED_FLOAT64_ARRAY, + OPCODE_TYPE_ADJUST_PACKED_STRING_ARRAY, + OPCODE_TYPE_ADJUST_PACKED_VECTOR2_ARRAY, + OPCODE_TYPE_ADJUST_PACKED_VECTOR3_ARRAY, + OPCODE_TYPE_ADJUST_PACKED_COLOR_ARRAY, OPCODE_ASSERT, OPCODE_BREAKPOINT, OPCODE_LINE, @@ -291,16 +397,18 @@ public: ADDR_BITS = 24, ADDR_MASK = ((1 << ADDR_BITS) - 1), ADDR_TYPE_MASK = ~ADDR_MASK, - ADDR_TYPE_SELF = 0, - ADDR_TYPE_CLASS = 1, + ADDR_TYPE_STACK = 0, + ADDR_TYPE_CONSTANT = 1, ADDR_TYPE_MEMBER = 2, - ADDR_TYPE_CLASS_CONSTANT = 3, - ADDR_TYPE_LOCAL_CONSTANT = 4, - ADDR_TYPE_STACK = 5, - ADDR_TYPE_STACK_VARIABLE = 6, - ADDR_TYPE_GLOBAL = 7, - ADDR_TYPE_NAMED_GLOBAL = 8, - ADDR_TYPE_NIL = 9 + }; + + enum FixedAddresses { + ADDR_STACK_SELF = 0, + ADDR_STACK_CLASS = 1, + ADDR_STACK_NIL = 2, + 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), }; enum Instruction { @@ -353,6 +461,8 @@ private: const GDScriptUtilityFunctions::FunctionPtr *_gds_utilities_ptr = nullptr; int _methods_count = 0; MethodBind **_methods_ptr = nullptr; + int _lambdas_count = 0; + GDScriptFunction **_lambdas_ptr = nullptr; const int *_code_ptr = nullptr; int _code_size = 0; int _argument_count = 0; @@ -382,10 +492,13 @@ private: Vector<Variant::ValidatedUtilityFunction> utilities; Vector<GDScriptUtilityFunctions::FunctionPtr> gds_utilities; Vector<MethodBind *> methods; + Vector<GDScriptFunction *> lambdas; Vector<int> code; Vector<GDScriptDataType> argument_types; GDScriptDataType return_type; + Map<int, Variant::Type> temporary_slots; + #ifdef TOOLS_ENABLED Vector<StringName> arg_names; Vector<Variant> default_arg_values; @@ -393,7 +506,7 @@ private: List<StackDebug> stack_debug; - _FORCE_INLINE_ Variant *_get_variant(int p_address, GDScriptInstance *p_instance, GDScript *p_script, Variant &self, Variant &static_ref, Variant *p_stack, String &r_error) const; + _FORCE_INLINE_ Variant *_get_variant(int p_address, GDScriptInstance *p_instance, Variant *p_stack, String &r_error) const; _FORCE_INLINE_ String _get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const; friend class GDScriptLanguage; @@ -428,7 +541,6 @@ public: #endif Vector<uint8_t> stack; int stack_size = 0; - Variant self; uint32_t alloca_size = 0; int ip = 0; int line = 0; @@ -485,8 +597,8 @@ public: ~GDScriptFunction(); }; -class GDScriptFunctionState : public Reference { - GDCLASS(GDScriptFunctionState, Reference); +class GDScriptFunctionState : public RefCounted { + GDCLASS(GDScriptFunctionState, RefCounted); friend class GDScriptFunction; GDScriptFunction *function = nullptr; GDScriptFunction::CallState state; diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp new file mode 100644 index 0000000000..0bc109b6e1 --- /dev/null +++ b/modules/gdscript/gdscript_lambda_callable.cpp @@ -0,0 +1,95 @@ +/*************************************************************************/ +/* gdscript_lambda_callable.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gdscript_lambda_callable.h" + +#include "core/templates/hashfuncs.h" +#include "gdscript.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; +} + +bool GDScriptLambdaCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) { + // Lambda callables are only compared by reference. + return p_a < p_b; +} + +uint32_t GDScriptLambdaCallable::hash() const { + return h; +} + +String GDScriptLambdaCallable::get_as_text() const { + if (function->get_name() != StringName()) { + return function->get_name().operator String() + "(lambda)"; + } + return "(anonymous lambda)"; +} + +CallableCustom::CompareEqualFunc GDScriptLambdaCallable::get_compare_equal_func() const { + return compare_equal; +} + +CallableCustom::CompareLessFunc GDScriptLambdaCallable::get_compare_less_func() const { + return compare_less; +} + +ObjectID GDScriptLambdaCallable::get_object() const { + return script->get_instance_id(); +} + +void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { + int captures_amount = captures.size(); + + if (captures_amount > 0) { + Vector<const Variant *> args; + args.resize(p_argcount + captures_amount); + for (int i = 0; i < captures_amount; i++) { + args.write[i] = &captures[i]; + } + for (int i = 0; i < p_argcount; i++) { + args.write[i + captures_amount] = p_arguments[i]; + } + + r_return_value = function->call(nullptr, args.ptrw(), args.size(), r_call_error); + r_call_error.argument -= captures_amount; + } else { + r_return_value = function->call(nullptr, p_arguments, p_argcount, r_call_error); + } +} + +GDScriptLambdaCallable::GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures) { + script = p_script; + function = p_function; + captures = p_captures; + + h = (uint32_t)hash_djb2_one_64((uint64_t)this); +} diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h new file mode 100644 index 0000000000..336778d549 --- /dev/null +++ b/modules/gdscript/gdscript_lambda_callable.h @@ -0,0 +1,65 @@ +/*************************************************************************/ +/* gdscript_lambda_callable.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GDSCRIPT_LAMBDA_CALLABLE +#define GDSCRIPT_LAMBDA_CALLABLE + +#include "core/object/ref_counted.h" +#include "core/templates/vector.h" +#include "core/variant/callable.h" +#include "core/variant/variant.h" + +class GDScript; +class GDScriptFunction; +class GDScriptInstance; + +class GDScriptLambdaCallable : public CallableCustom { + GDScriptFunction *function = nullptr; + Ref<GDScript> script; + uint32_t h; + + Vector<Variant> captures; + + static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b); + static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b); + +public: + uint32_t hash() const override; + String get_as_text() const override; + CompareEqualFunc get_compare_equal_func() const override; + CompareLessFunc get_compare_less_func() const override; + ObjectID get_object() const override; + void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; + + GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures); + virtual ~GDScriptLambdaCallable() = default; +}; + +#endif // GDSCRIPT_LAMBDA_CALLABLE diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index a77fb14064..1a22820c63 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -31,9 +31,9 @@ #include "gdscript_parser.h" #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 "core/os/file_access.h" #include "gdscript.h" #ifdef DEBUG_ENABLED @@ -61,9 +61,9 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { builtin_types["Vector3i"] = Variant::VECTOR3I; builtin_types["AABB"] = Variant::AABB; builtin_types["Plane"] = Variant::PLANE; - builtin_types["Quat"] = Variant::QUAT; + builtin_types["Quaternion"] = Variant::QUATERNION; builtin_types["Basis"] = Variant::BASIS; - builtin_types["Transform"] = Variant::TRANSFORM; + builtin_types["Transform3D"] = Variant::TRANSFORM3D; builtin_types["Color"] = Variant::COLOR; builtin_types["RID"] = Variant::RID; builtin_types["Object"] = Variant::OBJECT; @@ -94,8 +94,43 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { return Variant::VARIANT_MAX; } +// TODO: Move this to a central location (maybe core?). +static HashMap<StringName, StringName> underscore_map; +static const char *underscore_classes[] = { + "ClassDB", + "Directory", + "Engine", + "File", + "Geometry", + "GodotSharp", + "JSON", + "Marshalls", + "Mutex", + "OS", + "ResourceLoader", + "ResourceSaver", + "Semaphore", + "Thread", + "VisualScriptEditor", + nullptr, +}; +StringName GDScriptParser::get_real_class_name(const StringName &p_source) { + if (underscore_map.is_empty()) { + const char **class_name = underscore_classes; + while (*class_name != nullptr) { + underscore_map[*class_name] = String("_") + *class_name; + class_name++; + } + } + if (underscore_map.has(p_source)) { + return underscore_map[p_source]; + } + return p_source; +} + void GDScriptParser::cleanup() { builtin_types.clear(); + underscore_map.clear(); } void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const { @@ -109,12 +144,11 @@ void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const GDScriptParser::GDScriptParser() { // Register valid annotations. // TODO: Should this be static? - // TODO: Validate applicable types (e.g. a VARIABLE annotation that only applies to string variables). register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation); register_annotation(MethodInfo("@icon", { Variant::STRING, "icon_path" }), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation); register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation); // Export annotations. - register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_TYPE_STRING, Variant::NIL>); + register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>); register_annotation(MethodInfo("@export_enum", { Variant::STRING, "names" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::INT>, 0, true); register_annotation(MethodInfo("@export_file", { Variant::STRING, "filter" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, 1, true); register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>); @@ -123,15 +157,16 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_multiline"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_MULTILINE_TEXT, Variant::STRING>); register_annotation(MethodInfo("@export_placeholder"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>); register_annotation(MethodInfo("@export_range", { Variant::FLOAT, "min" }, { Variant::FLOAT, "max" }, { Variant::FLOAT, "step" }, { Variant::STRING, "slider1" }, { Variant::STRING, "slider2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, 3); - register_annotation(MethodInfo("@export_exp_range", { Variant::FLOAT, "min" }, { Variant::FLOAT, "max" }, { Variant::FLOAT, "step" }, { Variant::STRING, "slider1" }, { Variant::STRING, "slider2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_RANGE, Variant::FLOAT>, 3); register_annotation(MethodInfo("@export_exp_easing", { Variant::STRING, "hint1" }, { Variant::STRING, "hint2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, 2); register_annotation(MethodInfo("@export_color_no_alpha"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_COLOR_NO_ALPHA, Variant::COLOR>); register_annotation(MethodInfo("@export_node_path", { Variant::STRING, "type" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NODE_PATH_VALID_TYPES, Variant::NODE_PATH>, 1, true); register_annotation(MethodInfo("@export_flags", { Variant::STRING, "names" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FLAGS, Variant::INT>, 0, true); register_annotation(MethodInfo("@export_flags_2d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_RENDER, Variant::INT>); register_annotation(MethodInfo("@export_flags_2d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_PHYSICS, Variant::INT>); + register_annotation(MethodInfo("@export_flags_2d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_NAVIGATION, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); + register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>); // Networking. register_annotation(MethodInfo("@remote"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_REMOTE>); register_annotation(MethodInfo("@master"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_MASTER>); @@ -366,6 +401,8 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ } GDScriptTokenizer::Token GDScriptParser::advance() { + lambda_ended = false; // Empty marker since we're past the end in any case. + if (current.type == GDScriptTokenizer::Token::TK_EOF) { ERR_FAIL_COND_V_MSG(current.type == GDScriptTokenizer::Token::TK_EOF, current, "GDScript parser bug: Trying to advance past the end of stream."); } @@ -392,7 +429,7 @@ bool GDScriptParser::match(GDScriptTokenizer::Token::Type p_token_type) { return true; } -bool GDScriptParser::check(GDScriptTokenizer::Token::Type p_token_type) { +bool GDScriptParser::check(GDScriptTokenizer::Token::Type p_token_type) const { if (p_token_type == GDScriptTokenizer::Token::IDENTIFIER) { return current.is_identifier(); } @@ -407,7 +444,7 @@ bool GDScriptParser::consume(GDScriptTokenizer::Token::Type p_token_type, const return false; } -bool GDScriptParser::is_at_end() { +bool GDScriptParser::is_at_end() const { return check(GDScriptTokenizer::Token::TK_EOF); } @@ -458,16 +495,34 @@ void GDScriptParser::pop_multiline() { tokenizer.set_multiline_mode(multiline_stack.size() > 0 ? multiline_stack.back()->get() : false); } -bool GDScriptParser::is_statement_end() { +bool GDScriptParser::is_statement_end_token() const { return check(GDScriptTokenizer::Token::NEWLINE) || check(GDScriptTokenizer::Token::SEMICOLON) || check(GDScriptTokenizer::Token::TK_EOF); } +bool GDScriptParser::is_statement_end() const { + return lambda_ended || in_lambda || is_statement_end_token(); +} + void GDScriptParser::end_statement(const String &p_context) { bool found = false; while (is_statement_end() && !is_at_end()) { // Remove sequential newlines/semicolons. + if (is_statement_end_token()) { + // Only consume if this is an actual token. + advance(); + } else if (lambda_ended) { + lambda_ended = false; // Consume this "token". + found = true; + break; + } else { + if (!found) { + lambda_ended = true; // Mark the lambda as done since we found something else to end the statement. + found = true; + } + break; + } + found = true; - advance(); } if (!found && !is_at_end()) { push_error(vformat(R"(Expected end of statement after %s, found "%s" instead.)", p_context, current.get_name())); @@ -678,7 +733,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)() while (!annotation_stack.is_empty()) { AnnotationNode *last_annotation = annotation_stack.back()->get(); if (last_annotation->applies_to(p_target)) { - last_annotation->apply(this, member); member->annotations.push_front(last_annotation); annotation_stack.pop_back(); } else { @@ -776,6 +830,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper VariableNode *variable = alloc_node<VariableNode>(); variable->identifier = parse_identifier(); + variable->export_info.name = variable->identifier->name; if (match(GDScriptTokenizer::Token::COLON)) { if (check(GDScriptTokenizer::Token::NEWLINE)) { @@ -809,6 +864,9 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper if (match(GDScriptTokenizer::Token::EQUAL)) { // Initializer. variable->initializer = parse_expression(false); + if (variable->initializer == nullptr) { + push_error(R"(Expected expression for variable initial value after "=".)"); + } variable->assignments++; } @@ -822,8 +880,6 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper end_statement("variable declaration"); - variable->export_info.name = variable->identifier->name; - return variable; } @@ -1116,7 +1172,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)"); #ifdef TOOLS_ENABLED - // Enum values documentaion. + // Enum values documentation. for (int i = 0; i < enum_node->values.size(); i++) { if (i == enum_node->values.size() - 1) { // If close bracket is same line as last value. @@ -1145,36 +1201,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { return enum_node; } -GDScriptParser::FunctionNode *GDScriptParser::parse_function() { - bool _static = false; - if (previous.type == GDScriptTokenizer::Token::STATIC) { - // TODO: Improve message if user uses "static" with "var" or "const" - if (!consume(GDScriptTokenizer::Token::FUNC, R"(Expected "func" after "static".)")) { - return nullptr; - } - _static = true; - } - - FunctionNode *function = alloc_node<FunctionNode>(); - make_completion_context(COMPLETION_OVERRIDE_METHOD, function); - - if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) { - return nullptr; - } - - FunctionNode *previous_function = current_function; - current_function = function; - - function->identifier = parse_identifier(); - function->is_static = _static; - - push_multiline(true); - consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after function name.)"); - - SuiteNode *body = alloc_node<SuiteNode>(); - SuiteNode *previous_suite = current_suite; - current_suite = body; - +void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type) { if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) { bool default_used = false; do { @@ -1194,29 +1221,61 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() { continue; } } - if (function->parameters_indices.has(parameter->identifier->name)) { - push_error(vformat(R"(Parameter with name "%s" was already declared for this function.)", parameter->identifier->name)); + if (p_function->parameters_indices.has(parameter->identifier->name)) { + push_error(vformat(R"(Parameter with name "%s" was already declared for this %s.)", parameter->identifier->name, p_type)); } else { - function->parameters_indices[parameter->identifier->name] = function->parameters.size(); - function->parameters.push_back(parameter); - body->add_local(parameter); + p_function->parameters_indices[parameter->identifier->name] = p_function->parameters.size(); + p_function->parameters.push_back(parameter); + p_body->add_local(parameter, current_function); } } while (match(GDScriptTokenizer::Token::COMMA)); } pop_multiline(); - consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after function parameters.)*"); + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, vformat(R"*(Expected closing ")" after %s parameters.)*", p_type)); if (match(GDScriptTokenizer::Token::FORWARD_ARROW)) { - make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, function); - function->return_type = parse_type(true); - if (function->return_type == nullptr) { + make_completion_context(COMPLETION_TYPE_NAME_OR_VOID, p_function); + p_function->return_type = parse_type(true); + if (p_function->return_type == nullptr) { push_error(R"(Expected return type or "void" after "->".)"); } } // TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens. - consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after function declaration.)"); + consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after %s declaration.)", p_type)); +} + +GDScriptParser::FunctionNode *GDScriptParser::parse_function() { + bool _static = false; + if (previous.type == GDScriptTokenizer::Token::STATIC) { + // TODO: Improve message if user uses "static" with "var" or "const" + if (!consume(GDScriptTokenizer::Token::FUNC, R"(Expected "func" after "static".)")) { + return nullptr; + } + _static = true; + } + + FunctionNode *function = alloc_node<FunctionNode>(); + make_completion_context(COMPLETION_OVERRIDE_METHOD, function); + + if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) { + return nullptr; + } + + FunctionNode *previous_function = current_function; + current_function = function; + + function->identifier = parse_identifier(); + function->is_static = _static; + + SuiteNode *body = alloc_node<SuiteNode>(); + SuiteNode *previous_suite = current_suite; + current_suite = body; + + push_multiline(true); + consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after function name.)"); + parse_function_signature(function, body, "function"); current_suite = previous_suite; function->body = parse_suite("function declaration", body); @@ -1302,29 +1361,34 @@ bool GDScriptParser::register_annotation(const MethodInfo &p_info, uint32_t p_ta return true; } -GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, SuiteNode *p_suite) { +GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, SuiteNode *p_suite, bool p_for_lambda) { SuiteNode *suite = p_suite != nullptr ? p_suite : alloc_node<SuiteNode>(); suite->parent_block = current_suite; + suite->parent_function = current_function; current_suite = suite; bool multiline = false; - if (check(GDScriptTokenizer::Token::NEWLINE)) { + if (match(GDScriptTokenizer::Token::NEWLINE)) { multiline = true; } if (multiline) { - consume(GDScriptTokenizer::Token::NEWLINE, vformat(R"(Expected newline after %s.)", p_context)); - if (!consume(GDScriptTokenizer::Token::INDENT, vformat(R"(Expected indented block after %s.)", p_context))) { current_suite = suite->parent_block; return suite; } } + int error_count = 0; + do { Node *statement = parse_statement(); if (statement == nullptr) { + if (error_count++ > 100) { + push_error("Too many statement errors.", suite); + break; + } continue; } suite->statements.push_back(statement); @@ -1337,7 +1401,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, if (local.type != SuiteNode::Local::UNDEFINED) { push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), variable->identifier->name)); } - current_suite->add_local(variable); + current_suite->add_local(variable, current_function); break; } case Node::CONSTANT: { @@ -1352,19 +1416,29 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, } push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, constant->identifier->name)); } - current_suite->add_local(constant); + current_suite->add_local(constant, current_function); break; } default: break; } - } while (multiline && !check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()); + } while (multiline && !check(GDScriptTokenizer::Token::DEDENT) && !lambda_ended && !is_at_end()); if (multiline) { - consume(GDScriptTokenizer::Token::DEDENT, vformat(R"(Missing unindent at the end of %s.)", p_context)); + if (!lambda_ended) { + consume(GDScriptTokenizer::Token::DEDENT, vformat(R"(Missing unindent at the end of %s.)", p_context)); + + } else { + match(GDScriptTokenizer::Token::DEDENT); + } + } else if (previous.type == GDScriptTokenizer::Token::SEMICOLON) { + consume(GDScriptTokenizer::Token::NEWLINE, vformat(R"(Expected newline after ";" at the end of %s.)", p_context)); } + if (p_for_lambda) { + lambda_ended = true; + } current_suite = suite->parent_block; return suite; } @@ -1421,6 +1495,10 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { push_error(R"(Constructor cannot return a value.)"); } n_return->return_value = parse_expression(false); + } else if (in_lambda && !is_statement_end_token()) { + // Try to parse it anyway as this might not be the statement end in a lambda. + // If this fails the expression will be nullptr, but that's the same as no return, so it's fine. + n_return->return_value = parse_expression(false); } result = n_return; @@ -1449,10 +1527,18 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { default: { // Expression statement. ExpressionNode *expression = parse_expression(true); // Allow assignment here. + bool has_ended_lambda = false; if (expression == nullptr) { - push_error(vformat(R"(Expected statement, found "%s" instead.)", previous.get_name())); + if (in_lambda) { + // If it's not a valid expression beginning, it might be the continuation of the outer expression where this lambda is. + lambda_ended = true; + has_ended_lambda = true; + } else { + push_error(vformat(R"(Expected statement, found "%s" instead.)", previous.get_name())); + } } end_statement("expression"); + lambda_ended = lambda_ended || has_ended_lambda; result = expression; #ifdef DEBUG_ENABLED @@ -1476,7 +1562,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { if (unreachable && result != nullptr) { current_suite->has_unreachable_code = true; if (current_function) { - push_warning(result, GDScriptWarning::UNREACHABLE_CODE, current_function->identifier->name); + push_warning(result, GDScriptWarning::UNREACHABLE_CODE, current_function->identifier ? current_function->identifier->name : "<anonymous lambda>"); } else { // TODO: Properties setters and getters with unreachable code are not being warned } @@ -1561,7 +1647,7 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { SuiteNode *suite = alloc_node<SuiteNode>(); if (n_for->variable) { - suite->add_local(SuiteNode::Local(n_for->variable)); + suite->add_local(SuiteNode::Local(n_for->variable, current_function)); } suite->parent_for = n_for; @@ -1716,7 +1802,7 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { branch->patterns[0]->binds.get_key_list(&binds); for (List<StringName>::Element *E = binds.front(); E != nullptr; E = E->next()) { - SuiteNode::Local local(branch->patterns[0]->binds[E->get()]); + SuiteNode::Local local(branch->patterns[0]->binds[E->get()], current_function); suite->add_local(local); } } @@ -1916,7 +2002,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr // Completion can appear whenever an expression is expected. make_completion_context(COMPLETION_IDENTIFIER, nullptr); - GDScriptTokenizer::Token token = advance(); + GDScriptTokenizer::Token token = current; ParseFunction prefix_rule = get_rule(token.type)->prefix; if (prefix_rule == nullptr) { @@ -1924,6 +2010,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr return nullptr; } + advance(); // Only consume the token if there's a valid rule. + ExpressionNode *previous_operand = (this->*prefix_rule)(nullptr, p_can_assign); while (p_precedence <= get_rule(current.type)->precedence) { @@ -1965,6 +2053,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode if (current_suite != nullptr && current_suite->has_local(identifier->name)) { const SuiteNode::Local &declaration = current_suite->get_local(identifier->name); + + identifier->source_function = declaration.source_function; switch (declaration.type) { case SuiteNode::Local::CONSTANT: identifier->source = IdentifierNode::LOCAL_CONSTANT; @@ -2018,6 +2108,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_self(ExpressionNode *p_pre if (current_function && current_function->is_static) { push_error(R"(Cannot use "self" inside a static function.)"); } + if (in_lambda) { + push_error(R"(Cannot use "self" inside a lambda.)"); + } SelfNode *self = alloc_node<SelfNode>(); self->current_class = current_class; return self; @@ -2080,6 +2173,17 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_unary_operator(ExpressionN return operation; } +GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_not_in_operator(ExpressionNode *p_previous_operand, bool p_can_assign) { + // check that NOT is followed by IN by consuming it before calling parse_binary_operator which will only receive a plain IN + consume(GDScriptTokenizer::Token::IN, R"(Expected "in" after "not" in content-test operator.)"); + ExpressionNode *in_operation = parse_binary_operator(p_previous_operand, p_can_assign); + UnaryOpNode *operation = alloc_node<UnaryOpNode>(); + operation->operation = UnaryOpNode::OP_LOGIC_NOT; + operation->variant_op = Variant::OP_NOT; + operation->operand = in_operation; + return operation; +} + GDScriptParser::ExpressionNode *GDScriptParser::parse_binary_operator(ExpressionNode *p_previous_operand, bool p_can_assign) { GDScriptTokenizer::Token op = previous; BinaryOpNode *operation = alloc_node<BinaryOpNode>(); @@ -2392,6 +2496,8 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode push_error(R"(Expected "=" after dictionary key.)"); } } + key->is_constant = true; + key->reduced_value = static_cast<IdentifierNode *>(key)->name; break; case DictionaryNode::PYTHON_DICT: if (!match(GDScriptTokenizer::Token::COLON)) { @@ -2438,7 +2544,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode * if (for_completion) { bool is_builtin = false; - if (p_previous_operand->type == Node::IDENTIFIER) { + if (p_previous_operand && p_previous_operand->type == Node::IDENTIFIER) { const IdentifierNode *id = static_cast<const IdentifierNode *>(p_previous_operand); Variant::Type builtin_type = get_builtin_type(id->name); if (builtin_type < Variant::VARIANT_MAX) { @@ -2625,6 +2731,65 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_preload(ExpressionNode *p_ return preload; } +GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign) { + LambdaNode *lambda = alloc_node<LambdaNode>(); + lambda->parent_function = current_function; + FunctionNode *function = alloc_node<FunctionNode>(); + function->source_lambda = lambda; + + function->is_static = current_function != nullptr ? current_function->is_static : false; + + if (match(GDScriptTokenizer::Token::IDENTIFIER)) { + function->identifier = parse_identifier(); + } + + bool multiline_context = multiline_stack.back()->get(); + + // Reset the multiline stack since we don't want the multiline mode one in the lambda body. + push_multiline(false); + if (multiline_context) { + tokenizer.push_expression_indented_block(); + } + + push_multiline(true); // For the parameters. + if (function->identifier) { + consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after lambda name.)"); + } else { + consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected opening "(" after "func".)"); + } + + FunctionNode *previous_function = current_function; + current_function = function; + + SuiteNode *body = alloc_node<SuiteNode>(); + SuiteNode *previous_suite = current_suite; + current_suite = body; + + parse_function_signature(function, body, "lambda"); + + current_suite = previous_suite; + + bool previous_in_lambda = in_lambda; + in_lambda = true; + + function->body = parse_suite("lambda declaration", body, true); + + pop_multiline(); + + if (multiline_context) { + // If we're in multiline mode, we want to skip the spurious DEDENT and NEWLINE tokens. + while (check(GDScriptTokenizer::Token::DEDENT) || check(GDScriptTokenizer::Token::INDENT) || check(GDScriptTokenizer::Token::NEWLINE)) { + current = tokenizer.scan(); // Not advance() since we don't want to change the previous token. + } + tokenizer.pop_expression_indented_block(); + } + + current_function = previous_function; + in_lambda = previous_in_lambda; + lambda->function = function; + return lambda; +} + GDScriptParser::ExpressionNode *GDScriptParser::parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign) { // Just for better error messages. GDScriptTokenizer::Token::Type invalid = previous.type; @@ -2661,6 +2826,19 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { type->type_chain.push_back(type_element); + if (match(GDScriptTokenizer::Token::BRACKET_OPEN)) { + // Typed collection (like Array[int]). + type->container_type = parse_type(false); // Don't allow void for array element type. + if (type->container_type == nullptr) { + push_error(R"(Expected type for collection after "[".)"); + type = nullptr; + } else if (type->container_type->container_type != nullptr) { + push_error("Nested typed collections are not supported."); + } + consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected closing "]" after collection type.)"); + return type; + } + int chain_index = 1; while (match(GDScriptTokenizer::Token::PERIOD)) { make_completion_context(COMPLETION_TYPE_ATTRIBUTE, type, chain_index++); @@ -2906,7 +3084,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty // Logical { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_AND }, // AND, { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_OR }, // OR, - { &GDScriptParser::parse_unary_operator, nullptr, PREC_NONE }, // NOT, + { &GDScriptParser::parse_unary_operator, &GDScriptParser::parse_binary_not_in_operator, PREC_CONTENT_TEST }, // NOT, { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_AND }, // AMPERSAND_AMPERSAND, { nullptr, &GDScriptParser::parse_binary_operator, PREC_LOGIC_OR }, // PIPE_PIPE, { &GDScriptParser::parse_unary_operator, nullptr, PREC_NONE }, // BANG, @@ -2956,7 +3134,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty { nullptr, nullptr, PREC_NONE }, // CONST, { nullptr, nullptr, PREC_NONE }, // ENUM, { nullptr, nullptr, PREC_NONE }, // EXTENDS, - { nullptr, nullptr, PREC_NONE }, // FUNC, + { &GDScriptParser::parse_lambda, nullptr, PREC_NONE }, // FUNC, { nullptr, &GDScriptParser::parse_binary_operator, PREC_CONTENT_TEST }, // IN, { nullptr, &GDScriptParser::parse_binary_operator, PREC_TYPE_TEST }, // IS, { nullptr, nullptr, PREC_NONE }, // NAMESPACE, @@ -3005,7 +3183,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty // Avoid desync. static_assert(sizeof(rules) / sizeof(rules[0]) == GDScriptTokenizer::Token::TK_MAX, "Amount of parse rules don't match the amount of token types."); - // Let's assume this this never invalid, since nothing generates a TK_MAX. + // Let's assume this is never invalid, since nothing generates a TK_MAX. return &rules[p_token_type]; } @@ -3147,24 +3325,10 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } variable->exported = true; - // TODO: Improving setting type, especially for range hints, which can be int or float. + variable->export_info.type = t_type; variable->export_info.hint = t_hint; - if (p_annotation->name == "@export") { - if (variable->datatype_specifier == nullptr) { - if (variable->initializer == nullptr) { - push_error(R"(Cannot use "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation); - return false; - } - if (variable->initializer->type != Node::LITERAL) { - push_error(R"(To use "@export" annotation with type-less variable, the default value must be a literal.)", p_annotation); - return false; - } - variable->export_info.type = static_cast<LiteralNode *>(variable->initializer)->value.get_type(); - } // else: Actual type will be set by the analyzer, which can infer the proper type. - } - String hint_string; for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) { if (i > 0) { @@ -3175,6 +3339,86 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node variable->export_info.hint_string = hint_string; + // This is called after tne analyzer is done finding the type, so this should be set here. + DataType export_type = variable->get_datatype(); + + if (p_annotation->name == "@export") { + if (variable->datatype_specifier == nullptr && variable->initializer == nullptr) { + push_error(R"(Cannot use simple "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation); + return false; + } + + bool is_array = false; + + if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type()) { + export_type = export_type.get_container_element_type(); // Use inner type for. + is_array = true; + } + + if (export_type.is_variant() || export_type.has_no_type()) { + push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation); + return false; + } + + switch (export_type.kind) { + case GDScriptParser::DataType::BUILTIN: + variable->export_info.type = export_type.builtin_type; + variable->export_info.hint = PROPERTY_HINT_NONE; + variable->export_info.hint_string = Variant::get_type_name(export_type.builtin_type); + break; + case GDScriptParser::DataType::NATIVE: + if (ClassDB::is_parent_class(get_real_class_name(export_type.native_type), "Resource")) { + variable->export_info.type = Variant::OBJECT; + variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; + variable->export_info.hint_string = get_real_class_name(export_type.native_type); + } else { + push_error(R"(Export type can only be built-in, a resource, or an enum.)", variable); + return false; + } + break; + case GDScriptParser::DataType::ENUM: { + variable->export_info.type = Variant::INT; + variable->export_info.hint = PROPERTY_HINT_ENUM; + + String enum_hint_string; + for (const Map<StringName, int>::Element *E = export_type.enum_values.front(); E; E = E->next()) { + enum_hint_string += E->key().operator String().camelcase_to_underscore(true).capitalize().xml_escape(); + enum_hint_string += ":"; + enum_hint_string += String::num_int64(E->get()).xml_escape(); + + if (E->next()) { + enum_hint_string += ","; + } + } + + variable->export_info.hint_string = enum_hint_string; + } break; + default: + // TODO: Allow custom user resources. + push_error(R"(Export type can only be built-in, a resource, or an enum.)", variable); + break; + } + + if (is_array) { + String hint_prefix = itos(variable->export_info.type); + if (variable->export_info.hint) { + hint_prefix += "/" + itos(variable->export_info.hint); + } + variable->export_info.hint = PROPERTY_HINT_TYPE_STRING; + variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string; + variable->export_info.type = Variant::ARRAY; + } + } else { + // Validate variable type with export. + if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != t_type)) { + // Allow float/int conversion. + if ((t_type != Variant::FLOAT || export_type.builtin_type != Variant::INT) && (t_type != Variant::INT || export_type.builtin_type != Variant::FLOAT)) { + push_error(vformat(R"("%s" annotation requires a variable of type "%s" but type "%s" was given instead.)", p_annotation->name.operator String(), Variant::get_type_name(t_type), export_type.to_string()), variable); + return false; + } + } + } + return true; } @@ -3260,6 +3504,9 @@ String GDScriptParser::DataType::to_string() const { if (builtin_type == Variant::NIL) { return "null"; } + if (builtin_type == Variant::ARRAY && has_container_element_type()) { + return vformat("Array[%s]", container_element_type->to_string()); + } return Variant::get_type_name(builtin_type); case NATIVE: if (is_meta_type) { @@ -3623,6 +3870,10 @@ void GDScriptParser::TreePrinter::print_dictionary(DictionaryNode *p_dictionary) } void GDScriptParser::TreePrinter::print_expression(ExpressionNode *p_expression) { + if (p_expression == nullptr) { + push_text("<invalid expression>"); + return; + } switch (p_expression->type) { case Node::ARRAY: print_array(static_cast<ArrayNode *>(p_expression)); @@ -3651,6 +3902,9 @@ void GDScriptParser::TreePrinter::print_expression(ExpressionNode *p_expression) case Node::IDENTIFIER: print_identifier(static_cast<IdentifierNode *>(p_expression)); break; + case Node::LAMBDA: + print_lambda(static_cast<LambdaNode *>(p_expression)); + break; case Node::LITERAL: print_literal(static_cast<LiteralNode *>(p_expression)); break; @@ -3710,12 +3964,17 @@ void GDScriptParser::TreePrinter::print_for(ForNode *p_for) { decrease_indent(); } -void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function) { +void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function, const String &p_context) { for (const List<AnnotationNode *>::Element *E = p_function->annotations.front(); E != nullptr; E = E->next()) { print_annotation(E->get()); } - push_text("Function "); - print_identifier(p_function->identifier); + push_text(p_context); + push_text(" "); + if (p_function->identifier) { + print_identifier(p_function->identifier); + } else { + push_text("<anonymous>"); + } push_text("( "); for (int i = 0; i < p_function->parameters.size(); i++) { if (i > 0) { @@ -3769,6 +4028,18 @@ void GDScriptParser::TreePrinter::print_if(IfNode *p_if, bool p_is_elif) { } } +void GDScriptParser::TreePrinter::print_lambda(LambdaNode *p_lambda) { + print_function(p_lambda->function, "Lambda"); + push_text("| captures [ "); + for (int i = 0; i < p_lambda->captures.size(); i++) { + if (i > 0) { + push_text(" , "); + } + push_text(p_lambda->captures[i]->name.operator String()); + } + push_line(" ]"); +} + void GDScriptParser::TreePrinter::print_literal(LiteralNode *p_literal) { // Prefix for string types. switch (p_literal->value.get_type()) { diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index d59b68b602..9e0b60a407 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -33,7 +33,7 @@ #include "core/io/multiplayer_api.h" #include "core/io/resource.h" -#include "core/object/reference.h" +#include "core/object/ref_counted.h" #include "core/object/script_language.h" #include "core/string/string_name.h" #include "core/string/ustring.h" @@ -76,6 +76,7 @@ public: struct GetNodeNode; struct IdentifierNode; struct IfNode; + struct LambdaNode; struct LiteralNode; struct MatchNode; struct MatchBranchNode; @@ -94,7 +95,12 @@ public: struct VariableNode; struct WhileNode; - struct DataType { + class DataType { + private: + // Private access so we can control memory management. + DataType *container_element_type = nullptr; + + public: enum Kind { BUILTIN, NATIVE, @@ -104,7 +110,6 @@ public: ENUM_VALUE, // Value from enumeration. VARIANT, // Can be any type. UNRESOLVED, - // TODO: Enum }; Kind kind = UNRESOLVED; @@ -128,7 +133,7 @@ public: ClassNode *class_type = nullptr; MethodInfo method_info; // For callable/signals. - HashMap<StringName, int> enum_values; // For enums. + Map<StringName, int> enum_values; // For enums. _FORCE_INLINE_ bool is_set() const { return kind != UNRESOLVED; } _FORCE_INLINE_ bool has_no_type() const { return type_source == UNDETECTED; } @@ -136,6 +141,26 @@ public: _FORCE_INLINE_ bool is_hard_type() const { return type_source > INFERRED; } String to_string() const; + _FORCE_INLINE_ void set_container_element_type(const DataType &p_type) { + container_element_type = memnew(DataType(p_type)); + } + + _FORCE_INLINE_ DataType get_container_element_type() const { + ERR_FAIL_COND_V(container_element_type == nullptr, DataType()); + return *container_element_type; + } + + _FORCE_INLINE_ bool has_container_element_type() const { + return container_element_type != nullptr; + } + + _FORCE_INLINE_ void unset_container_element_type() { + if (container_element_type) { + memdelete(container_element_type); + }; + container_element_type = nullptr; + } + bool operator==(const DataType &p_other) const { if (type_source == UNDETECTED || p_other.type_source == UNDETECTED) { return true; // Can be consireded equal for parsing purposes. @@ -173,6 +198,37 @@ public: bool operator!=(const DataType &p_other) const { return !(this->operator==(p_other)); } + + DataType &operator=(const DataType &p_other) { + kind = p_other.kind; + type_source = p_other.type_source; + is_constant = p_other.is_constant; + is_meta_type = p_other.is_meta_type; + is_coroutine = p_other.is_coroutine; + builtin_type = p_other.builtin_type; + native_type = p_other.native_type; + enum_type = p_other.enum_type; + script_type = p_other.script_type; + script_path = p_other.script_path; + class_type = p_other.class_type; + method_info = p_other.method_info; + enum_values = p_other.enum_values; + unset_container_element_type(); + if (p_other.has_container_element_type()) { + set_container_element_type(p_other.get_container_element_type()); + } + return *this; + } + + DataType() = default; + + DataType(const DataType &p_other) { + *this = p_other; + } + + ~DataType() { + unset_container_element_type(); + } }; struct ParserError { @@ -212,6 +268,7 @@ public: GET_NODE, IDENTIFIER, IF, + LAMBDA, LITERAL, MATCH, MATCH_BRANCH, @@ -313,6 +370,7 @@ public: Variant::Operator variant_op = Variant::OP_MAX; ExpressionNode *assignee = nullptr; ExpressionNode *assigned_value = nullptr; + bool use_conversion_assign = false; AssignmentNode() { type = ASSIGNMENT; @@ -673,6 +731,7 @@ public: bool is_coroutine = false; MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; MethodInfo info; + LambdaNode *source_lambda = nullptr; #ifdef TOOLS_ENABLED Vector<Variant> default_arg_values; String doc_description; @@ -716,6 +775,7 @@ public: VariableNode *variable_source; IdentifierNode *bind_source; }; + FunctionNode *source_function = nullptr; int usages = 0; // Useful for binds/iterator variable. @@ -734,6 +794,21 @@ public: } }; + struct LambdaNode : public ExpressionNode { + FunctionNode *function = nullptr; + FunctionNode *parent_function = nullptr; + Vector<IdentifierNode *> captures; + Map<StringName, int> captures_indices; + + bool has_name() const { + return function && function->identifier; + } + + LambdaNode() { + type = LAMBDA; + } + }; + struct LiteralNode : public ExpressionNode { Variant value; @@ -887,6 +962,7 @@ public: IdentifierNode *bind; }; StringName name; + FunctionNode *source_function = nullptr; int start_line = 0, end_line = 0; int start_column = 0, end_column = 0; @@ -896,10 +972,11 @@ public: String get_name() const; Local() {} - Local(ConstantNode *p_constant) { + Local(ConstantNode *p_constant, FunctionNode *p_source_function) { type = CONSTANT; constant = p_constant; name = p_constant->identifier->name; + source_function = p_source_function; start_line = p_constant->start_line; end_line = p_constant->end_line; @@ -908,10 +985,11 @@ public: leftmost_column = p_constant->leftmost_column; rightmost_column = p_constant->rightmost_column; } - Local(VariableNode *p_variable) { + Local(VariableNode *p_variable, FunctionNode *p_source_function) { type = VARIABLE; variable = p_variable; name = p_variable->identifier->name; + source_function = p_source_function; start_line = p_variable->start_line; end_line = p_variable->end_line; @@ -920,10 +998,11 @@ public: leftmost_column = p_variable->leftmost_column; rightmost_column = p_variable->rightmost_column; } - Local(ParameterNode *p_parameter) { + Local(ParameterNode *p_parameter, FunctionNode *p_source_function) { type = PARAMETER; parameter = p_parameter; name = p_parameter->identifier->name; + source_function = p_source_function; start_line = p_parameter->start_line; end_line = p_parameter->end_line; @@ -932,10 +1011,11 @@ public: leftmost_column = p_parameter->leftmost_column; rightmost_column = p_parameter->rightmost_column; } - Local(IdentifierNode *p_identifier) { + Local(IdentifierNode *p_identifier, FunctionNode *p_source_function) { type = FOR_VARIABLE; bind = p_identifier; name = p_identifier->name; + source_function = p_source_function; start_line = p_identifier->start_line; end_line = p_identifier->end_line; @@ -960,9 +1040,9 @@ public: bool has_local(const StringName &p_name) const; const Local &get_local(const StringName &p_name) const; template <class T> - void add_local(T *p_local) { + void add_local(T *p_local, FunctionNode *p_source_function) { locals_indices[p_local->identifier->name] = locals.size(); - locals.push_back(Local(p_local)); + locals.push_back(Local(p_local, p_source_function)); } void add_local(const Local &p_local) { locals_indices[p_local.name] = locals.size(); @@ -987,6 +1067,7 @@ public: struct TypeNode : public Node { Vector<IdentifierNode *> type_chain; + TypeNode *container_type = nullptr; TypeNode() { type = TYPE; @@ -1039,6 +1120,7 @@ public: MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; int assignments = 0; int usages = 0; + bool use_conversion_assign = false; #ifdef TOOLS_ENABLED String doc_description; #endif // TOOLS_ENABLED @@ -1135,6 +1217,8 @@ private: CompletionCall completion_call; List<CompletionCall> completion_call_stack; bool passed_cursor = false; + bool in_lambda = false; + bool lambda_ended = false; // Marker for when a lambda ends, to apply an end of statement if needed. typedef bool (GDScriptParser::*AnnotationAction)(const AnnotationNode *p_annotation, Node *p_target); struct AnnotationInfo { @@ -1222,10 +1306,11 @@ private: GDScriptTokenizer::Token advance(); bool match(GDScriptTokenizer::Token::Type p_token_type); - bool check(GDScriptTokenizer::Token::Type p_token_type); + bool check(GDScriptTokenizer::Token::Type p_token_type) const; bool consume(GDScriptTokenizer::Token::Type p_token_type, const String &p_error_message); - bool is_at_end(); - bool is_statement_end(); + bool is_at_end() const; + bool is_statement_end_token() const; + bool is_statement_end() const; void end_statement(const String &p_context); void synchronize(); void push_multiline(bool p_state); @@ -1243,7 +1328,8 @@ private: EnumNode *parse_enum(); ParameterNode *parse_parameter(); FunctionNode *parse_function(); - SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr); + void parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type); + SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr, bool p_for_lambda = false); // Annotations AnnotationNode *parse_annotation(uint32_t p_valid_targets); bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, int p_optional_arguments = 0, bool p_is_vararg = false); @@ -1285,6 +1371,7 @@ private: ExpressionNode *parse_builtin_constant(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_unary_operator(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_binary_operator(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_binary_not_in_operator(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_ternary_operator(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_assignment(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_array(ExpressionNode *p_previous_operand, bool p_can_assign); @@ -1297,6 +1384,7 @@ private: ExpressionNode *parse_await(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign); TypeNode *parse_type(bool p_allow_void = false); #ifdef TOOLS_ENABLED @@ -1312,6 +1400,7 @@ public: ClassNode *get_tree() const { return head; } bool is_tool() const { return _is_tool; } static Variant::Type get_builtin_type(const StringName &p_type); + static StringName get_real_class_name(const StringName &p_source); CompletionContext get_completion_context() const { return completion_context; } CompletionCall get_completion_call() const { return completion_call; } @@ -1357,10 +1446,11 @@ public: void print_expression(ExpressionNode *p_expression); void print_enum(EnumNode *p_enum); void print_for(ForNode *p_for); - void print_function(FunctionNode *p_function); + void print_function(FunctionNode *p_function, const String &p_context = "Function"); void print_get_node(GetNodeNode *p_get_node); void print_if(IfNode *p_if, bool p_is_elif = false); void print_identifier(IdentifierNode *p_identifier); + void print_lambda(LambdaNode *p_lambda); void print_literal(LiteralNode *p_literal); void print_match(MatchNode *p_match); void print_match_branch(MatchBranchNode *p_match_branch); diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 315b8ee3b4..3f14156dfa 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -242,6 +242,16 @@ void GDScriptTokenizer::set_multiline_mode(bool p_state) { multiline_mode = p_state; } +void GDScriptTokenizer::push_expression_indented_block() { + indent_stack_stack.push_back(indent_stack); +} + +void GDScriptTokenizer::pop_expression_indented_block() { + ERR_FAIL_COND(indent_stack_stack.size() == 0); + indent_stack = indent_stack_stack.back()->get(); + indent_stack_stack.pop_back(); +} + int GDScriptTokenizer::get_cursor_line() const { return cursor_line; } @@ -633,6 +643,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { push_error(error); } previous_was_underscore = true; + } else { + previous_was_underscore = false; } _advance(); } @@ -704,6 +716,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { push_error(error); } previous_was_underscore = true; + } else { + previous_was_underscore = false; } _advance(); } @@ -898,6 +912,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { _advance(); _advance(); break; + } else { + // Not a multiline string termination, add consumed quote. + result += quote_char; } } else { // Ended single-line string. @@ -1169,7 +1186,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { if (pending_newline) { pending_newline = false; if (!multiline_mode) { - // Don't return newline tokens on multine mode. + // Don't return newline tokens on multiline mode. return last_newline; } } diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index bea4b14019..84b82c07f0 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -217,6 +217,7 @@ private: Token last_newline; int pending_indents = 0; List<int> indent_stack; + List<List<int>> indent_stack_stack; // For lambdas, which require manipulating the indentation point. List<char32_t> paren_stack; char32_t indent_char = '\0'; int position = 0; @@ -263,6 +264,8 @@ public: void set_multiline_mode(bool p_state); bool is_past_cursor() const; static String get_token_name(Token::Type p_token_type); + void push_expression_indented_block(); // For lambdas, or blocks inside expressions. + void pop_expression_indented_block(); // For lambdas, or blocks inside expressions. GDScriptTokenizer(); }; diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index 348d221352..64c629662c 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -298,7 +298,7 @@ struct GDScriptUtilityFunctionsDefinitions { sname.push_back(p->name); p = p->_owner; } - sname.invert(); + sname.reverse(); if (!p->path.is_resource_file()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 4e098d7a6d..8a261a88e3 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -33,23 +33,24 @@ #include "core/core_string_names.h" #include "core/os/os.h" #include "gdscript.h" +#include "gdscript_lambda_callable.h" -Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_instance, GDScript *p_script, Variant &self, Variant &static_ref, Variant *p_stack, String &r_error) const { +Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_instance, Variant *p_stack, String &r_error) const { int address = p_address & ADDR_MASK; //sequential table (jump table generated by compiler) switch ((p_address & ADDR_TYPE_MASK) >> ADDR_BITS) { - case ADDR_TYPE_SELF: { + case ADDR_TYPE_STACK: { #ifdef DEBUG_ENABLED - if (unlikely(!p_instance)) { - r_error = "Cannot access self without instance."; - return nullptr; - } + ERR_FAIL_INDEX_V(address, _stack_size, nullptr); #endif - return &self; + return &p_stack[address]; } break; - case ADDR_TYPE_CLASS: { - return &static_ref; + case ADDR_TYPE_CONSTANT: { +#ifdef DEBUG_ENABLED + ERR_FAIL_INDEX_V(address, _constant_count, nullptr); +#endif + return &_constants_ptr[address]; } break; case ADDR_TYPE_MEMBER: { #ifdef DEBUG_ENABLED @@ -61,65 +62,6 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta //member indexing is O(1) return &p_instance->members.write[address]; } break; - case ADDR_TYPE_CLASS_CONSTANT: { - //todo change to index! - GDScript *s = p_script; -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, _global_names_count, nullptr); -#endif - const StringName *sn = &_global_names_ptr[address]; - - while (s) { - GDScript *o = s; - while (o) { - Map<StringName, Variant>::Element *E = o->constants.find(*sn); - if (E) { - return &E->get(); - } - o = o->_owner; - } - s = s->_base; - } - - ERR_FAIL_V_MSG(nullptr, "GDScriptCompiler bug."); - } break; - case ADDR_TYPE_LOCAL_CONSTANT: { -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, _constant_count, nullptr); -#endif - return &_constants_ptr[address]; - } break; - case ADDR_TYPE_STACK: - case ADDR_TYPE_STACK_VARIABLE: { -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, _stack_size, nullptr); -#endif - return &p_stack[address]; - } break; - case ADDR_TYPE_GLOBAL: { -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, GDScriptLanguage::get_singleton()->get_global_array_size(), nullptr); -#endif - return &GDScriptLanguage::get_singleton()->get_global_array()[address]; - } break; -#ifdef TOOLS_ENABLED - case ADDR_TYPE_NAMED_GLOBAL: { -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, _global_names_count, nullptr); -#endif - StringName id = _global_names_ptr[address]; - - if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(id)) { - return (Variant *)&GDScriptLanguage::get_singleton()->get_named_globals_map()[id]; - } else { - r_error = "Autoload singleton '" + String(id) + "' has been removed."; - return nullptr; - } - } break; -#endif - case ADDR_TYPE_NIL: { - return &nil; - } break; } ERR_FAIL_V_MSG(nullptr, "Bad code! (unknown addressing mode)."); @@ -127,6 +69,17 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta } #ifdef DEBUG_ENABLED +static String _get_script_name(const Ref<Script> p_script) { + Ref<GDScript> gdscript = p_script; + if (gdscript.is_valid()) { + return gdscript->get_script_class_name(); + } else if (p_script->get_name().is_empty()) { + return p_script->get_path().get_file(); + } else { + return p_script->get_name(); + } +} + static String _get_var_type(const Variant *p_var) { String basestr; @@ -140,15 +93,30 @@ static String _get_var_type(const Variant *p_var) { basestr = "previously freed"; } } else { + basestr = bobj->get_class(); if (bobj->get_script_instance()) { - basestr = bobj->get_class() + " (" + bobj->get_script_instance()->get_script()->get_path().get_file() + ")"; - } else { - basestr = bobj->get_class(); + basestr += " (" + _get_script_name(bobj->get_script_instance()->get_script()) + ")"; } } } else { - basestr = Variant::get_type_name(p_var->get_type()); + if (p_var->get_type() == Variant::ARRAY) { + basestr = "Array"; + const Array *p_array = VariantInternal::get_array(p_var); + Variant::Type builtin_type = (Variant::Type)p_array->get_typed_builtin(); + StringName native_type = p_array->get_typed_class_name(); + Ref<Script> script_type = p_array->get_typed_script(); + + if (script_type.is_valid() && script_type->is_valid()) { + basestr += "[" + _get_script_name(script_type) + "]"; + } else if (native_type != StringName()) { + basestr += "[" + native_type.operator String() + "]"; + } else if (builtin_type != Variant::NIL) { + basestr += "[" + Variant::get_type_name(builtin_type) + "]"; + } + } else { + basestr = Variant::get_type_name(p_var->get_type()); + } } return basestr; @@ -184,6 +152,44 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const return err_text; } +void (*type_init_function_table[])(Variant *) = { + nullptr, // NIL (shouldn't be called). + &VariantInitializer<bool>::init, // BOOL. + &VariantInitializer<int64_t>::init, // INT. + &VariantInitializer<double>::init, // FLOAT. + &VariantInitializer<String>::init, // STRING. + &VariantInitializer<Vector2>::init, // VECTOR2. + &VariantInitializer<Vector2i>::init, // VECTOR2I. + &VariantInitializer<Rect2>::init, // RECT2. + &VariantInitializer<Rect2i>::init, // RECT2I. + &VariantInitializer<Vector3>::init, // VECTOR3. + &VariantInitializer<Vector3i>::init, // VECTOR3I. + &VariantInitializer<Transform2D>::init, // TRANSFORM2D. + &VariantInitializer<Plane>::init, // PLANE. + &VariantInitializer<Quaternion>::init, // QUATERNION. + &VariantInitializer<AABB>::init, // AABB. + &VariantInitializer<Basis>::init, // BASIS. + &VariantInitializer<Transform3D>::init, // TRANSFORM3D. + &VariantInitializer<Color>::init, // COLOR. + &VariantInitializer<StringName>::init, // STRING_NAME. + &VariantInitializer<NodePath>::init, // NODE_PATH. + &VariantInitializer<RID>::init, // RID. + &VariantTypeAdjust<Object *>::adjust, // OBJECT. + &VariantInitializer<Callable>::init, // CALLABLE. + &VariantInitializer<Signal>::init, // SIGNAL. + &VariantInitializer<Dictionary>::init, // DICTIONARY. + &VariantInitializer<Array>::init, // ARRAY. + &VariantInitializer<PackedByteArray>::init, // PACKED_BYTE_ARRAY. + &VariantInitializer<PackedInt32Array>::init, // PACKED_INT32_ARRAY. + &VariantInitializer<PackedInt64Array>::init, // PACKED_INT64_ARRAY. + &VariantInitializer<PackedFloat32Array>::init, // PACKED_FLOAT32_ARRAY. + &VariantInitializer<PackedFloat64Array>::init, // PACKED_FLOAT64_ARRAY. + &VariantInitializer<PackedStringArray>::init, // PACKED_STRING_ARRAY. + &VariantInitializer<PackedVector2Array>::init, // PACKED_VECTOR2_ARRAY. + &VariantInitializer<PackedVector3Array>::init, // PACKED_VECTOR3_ARRAY. + &VariantInitializer<PackedColorArray>::init, // PACKED_COLOR_ARRAY. +}; + #if defined(__GNUC__) #define OPCODES_TABLE \ static const void *switch_table_ops[] = { \ @@ -207,6 +213,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const &&OPCODE_ASSIGN_TRUE, \ &&OPCODE_ASSIGN_FALSE, \ &&OPCODE_ASSIGN_TYPED_BUILTIN, \ + &&OPCODE_ASSIGN_TYPED_ARRAY, \ &&OPCODE_ASSIGN_TYPED_NATIVE, \ &&OPCODE_ASSIGN_TYPED_SCRIPT, \ &&OPCODE_CAST_TO_BUILTIN, \ @@ -215,6 +222,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const &&OPCODE_CONSTRUCT, \ &&OPCODE_CONSTRUCT_VALIDATED, \ &&OPCODE_CONSTRUCT_ARRAY, \ + &&OPCODE_CONSTRUCT_TYPED_ARRAY, \ &&OPCODE_CONSTRUCT_DICTIONARY, \ &&OPCODE_CALL, \ &&OPCODE_CALL_RETURN, \ @@ -226,6 +234,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const &&OPCODE_CALL_SELF_BASE, \ &&OPCODE_CALL_METHOD_BIND, \ &&OPCODE_CALL_METHOD_BIND_RET, \ + &&OPCODE_CALL_BUILTIN_STATIC, \ &&OPCODE_CALL_PTRCALL_NO_RETURN, \ &&OPCODE_CALL_PTRCALL_BOOL, \ &&OPCODE_CALL_PTRCALL_INT, \ @@ -239,10 +248,10 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const &&OPCODE_CALL_PTRCALL_VECTOR3I, \ &&OPCODE_CALL_PTRCALL_TRANSFORM2D, \ &&OPCODE_CALL_PTRCALL_PLANE, \ - &&OPCODE_CALL_PTRCALL_QUAT, \ + &&OPCODE_CALL_PTRCALL_QUATERNION, \ &&OPCODE_CALL_PTRCALL_AABB, \ &&OPCODE_CALL_PTRCALL_BASIS, \ - &&OPCODE_CALL_PTRCALL_TRANSFORM, \ + &&OPCODE_CALL_PTRCALL_TRANSFORM3D, \ &&OPCODE_CALL_PTRCALL_COLOR, \ &&OPCODE_CALL_PTRCALL_STRING_NAME, \ &&OPCODE_CALL_PTRCALL_NODE_PATH, \ @@ -263,11 +272,16 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const &&OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY, \ &&OPCODE_AWAIT, \ &&OPCODE_AWAIT_RESUME, \ + &&OPCODE_CREATE_LAMBDA, \ &&OPCODE_JUMP, \ &&OPCODE_JUMP_IF, \ &&OPCODE_JUMP_IF_NOT, \ &&OPCODE_JUMP_TO_DEF_ARGUMENT, \ &&OPCODE_RETURN, \ + &&OPCODE_RETURN_TYPED_BUILTIN, \ + &&OPCODE_RETURN_TYPED_ARRAY, \ + &&OPCODE_RETURN_TYPED_NATIVE, \ + &&OPCODE_RETURN_TYPED_SCRIPT, \ &&OPCODE_ITERATE_BEGIN, \ &&OPCODE_ITERATE_BEGIN_INT, \ &&OPCODE_ITERATE_BEGIN_FLOAT, \ @@ -308,6 +322,41 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const &&OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, \ &&OPCODE_ITERATE_PACKED_COLOR_ARRAY, \ &&OPCODE_ITERATE_OBJECT, \ + &&OPCODE_STORE_NAMED_GLOBAL, \ + &&OPCODE_TYPE_ADJUST_BOOL, \ + &&OPCODE_TYPE_ADJUST_INT, \ + &&OPCODE_TYPE_ADJUST_FLOAT, \ + &&OPCODE_TYPE_ADJUST_STRING, \ + &&OPCODE_TYPE_ADJUST_VECTOR2, \ + &&OPCODE_TYPE_ADJUST_VECTOR2I, \ + &&OPCODE_TYPE_ADJUST_RECT2, \ + &&OPCODE_TYPE_ADJUST_RECT2I, \ + &&OPCODE_TYPE_ADJUST_VECTOR3, \ + &&OPCODE_TYPE_ADJUST_VECTOR3I, \ + &&OPCODE_TYPE_ADJUST_TRANSFORM2D, \ + &&OPCODE_TYPE_ADJUST_PLANE, \ + &&OPCODE_TYPE_ADJUST_QUATERNION, \ + &&OPCODE_TYPE_ADJUST_AABB, \ + &&OPCODE_TYPE_ADJUST_BASIS, \ + &&OPCODE_TYPE_ADJUST_TRANSFORM, \ + &&OPCODE_TYPE_ADJUST_COLOR, \ + &&OPCODE_TYPE_ADJUST_STRING_NAME, \ + &&OPCODE_TYPE_ADJUST_NODE_PATH, \ + &&OPCODE_TYPE_ADJUST_RID, \ + &&OPCODE_TYPE_ADJUST_OBJECT, \ + &&OPCODE_TYPE_ADJUST_CALLABLE, \ + &&OPCODE_TYPE_ADJUST_SIGNAL, \ + &&OPCODE_TYPE_ADJUST_DICTIONARY, \ + &&OPCODE_TYPE_ADJUST_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_BYTE_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_INT32_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_INT64_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_FLOAT32_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_FLOAT64_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_STRING_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_VECTOR2_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_VECTOR3_ARRAY, \ + &&OPCODE_TYPE_ADJUST_PACKED_COLOR_ARRAY, \ &&OPCODE_ASSERT, \ &&OPCODE_BREAKPOINT, \ &&OPCODE_LINE, \ @@ -349,7 +398,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const #define OP_GET_VECTOR3I get_vector3i #define OP_GET_RECT2 get_rect2 #define OP_GET_RECT2I get_rect2i -#define OP_GET_QUAT get_quat +#define OP_GET_QUATERNION get_quaternion #define OP_GET_COLOR get_color #define OP_GET_STRING get_string #define OP_GET_STRING_NAME get_string_name @@ -367,7 +416,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const #define OP_GET_PACKED_VECTOR2_ARRAY get_vector2_array #define OP_GET_PACKED_VECTOR3_ARRAY get_vector3_array #define OP_GET_PACKED_COLOR_ARRAY get_color_array -#define OP_GET_TRANSFORM get_transform +#define OP_GET_TRANSFORM3D get_transform #define OP_GET_TRANSFORM2D get_transform2d #define OP_GET_PLANE get_plane #define OP_GET_AABB get_aabb @@ -383,11 +432,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a r_err.error = Callable::CallError::CALL_OK; - Variant self; - Variant static_ref; Variant retvalue; Variant *stack = nullptr; - Variant **instruction_args; + Variant **instruction_args = nullptr; const void **call_args_ptr = nullptr; int defarg = 0; @@ -412,7 +459,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a script = p_state->script; p_instance = p_state->instance; defarg = p_state->defarg; - self = p_state->self; } else { if (p_argcount != _argument_count) { @@ -430,55 +476,49 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } } - alloca_size = sizeof(Variant *) * _instruction_args_size + sizeof(Variant) * _stack_size; + // Add 3 here for self, class, and nil. + alloca_size = sizeof(Variant *) * 3 + sizeof(Variant *) * _instruction_args_size + sizeof(Variant) * _stack_size; - if (alloca_size) { - uint8_t *aptr = (uint8_t *)alloca(alloca_size); + uint8_t *aptr = (uint8_t *)alloca(alloca_size); + stack = (Variant *)aptr; - if (_stack_size) { - stack = (Variant *)aptr; - for (int i = 0; i < p_argcount; i++) { - if (!argument_types[i].has_type) { - memnew_placement(&stack[i], Variant(*p_args[i])); - continue; - } - - if (!argument_types[i].is_type(*p_args[i], true)) { - r_err.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_err.argument = i; - r_err.expected = argument_types[i].kind == GDScriptDataType::BUILTIN ? argument_types[i].builtin_type : Variant::OBJECT; - return Variant(); - } - if (argument_types[i].kind == GDScriptDataType::BUILTIN) { - Variant arg; - Variant::construct(argument_types[i].builtin_type, arg, &p_args[i], 1, r_err); - memnew_placement(&stack[i], Variant(arg)); - } else { - memnew_placement(&stack[i], Variant(*p_args[i])); - } - } - for (int i = p_argcount; i < _stack_size; i++) { - memnew_placement(&stack[i], Variant); - } - } else { - stack = nullptr; + for (int i = 0; i < p_argcount; i++) { + if (!argument_types[i].has_type) { + memnew_placement(&stack[i + 3], Variant(*p_args[i])); + continue; } - if (_instruction_args_size) { - instruction_args = (Variant **)&aptr[sizeof(Variant) * _stack_size]; + if (!argument_types[i].is_type(*p_args[i], true)) { + r_err.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_err.argument = i; + r_err.expected = argument_types[i].kind == GDScriptDataType::BUILTIN ? argument_types[i].builtin_type : Variant::OBJECT; + return Variant(); + } + if (argument_types[i].kind == GDScriptDataType::BUILTIN) { + Variant arg; + Variant::construct(argument_types[i].builtin_type, arg, &p_args[i], 1, r_err); + memnew_placement(&stack[i + 3], Variant(arg)); } else { - instruction_args = nullptr; + memnew_placement(&stack[i + 3], Variant(*p_args[i])); } + } + for (int i = p_argcount + 3; i < _stack_size; i++) { + memnew_placement(&stack[i], Variant); + } + memnew_placement(&stack[ADDR_STACK_NIL], Variant); + + if (_instruction_args_size) { + instruction_args = (Variant **)&aptr[sizeof(Variant) * _stack_size]; } else { - stack = nullptr; instruction_args = nullptr; } if (p_instance) { - self = p_instance->owner; + memnew_placement(&stack[ADDR_STACK_SELF], Variant(p_instance->owner)); script = p_instance->script.ptr(); } else { + memnew_placement(&stack[ADDR_STACK_SELF], Variant); script = _script; } } @@ -488,7 +528,11 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a call_args_ptr = nullptr; } - static_ref = script; + memnew_placement(&stack[ADDR_STACK_CLASS], Variant(script)); + + for (const Map<int, Variant::Type>::Element *E = temporary_slots.front(); E; E = E->next()) { + type_init_function_table[E->get()](&stack[E->key()]); + } String err_text; @@ -509,10 +553,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #define CHECK_SPACE(m_space) \ GD_ERR_BREAK((ip + m_space) > _code_size) -#define GET_VARIANT_PTR(m_v, m_code_ofs) \ - Variant *m_v; \ - m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, static_ref, stack, err_text); \ - if (unlikely(!m_v)) \ +#define GET_VARIANT_PTR(m_v, m_code_ofs) \ + Variant *m_v; \ + m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, stack, err_text); \ + if (unlikely(!m_v)) \ OPCODE_BREAK; #else @@ -520,7 +564,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #define CHECK_SPACE(m_space) #define GET_VARIANT_PTR(m_v, m_code_ofs) \ Variant *m_v; \ - m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, script, self, static_ref, stack, err_text); + m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, stack, err_text); #endif @@ -544,7 +588,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED OPCODE_WHILE(ip < _code_size) { - int last_opcode = _code_ptr[ip]; + int last_opcode = _code_ptr[ip] & INSTR_MASK; #else OPCODE_WHILE(true) { #endif @@ -649,7 +693,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (scr_B) { //if B is a script, the only valid condition is that A has an instance which inherits from the script - //in other situation, this shoul return false. + //in other situation, this should return false. if (obj_A->get_script_instance() && obj_A->get_script_instance()->get_language() == GDScriptLanguage::get_singleton()) { GDScript *cmp = static_cast<GDScript *>(obj_A->get_script_instance()->get_script().ptr()); @@ -1077,6 +1121,31 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_ASSIGN_TYPED_ARRAY) { + CHECK_SPACE(3); + GET_INSTRUCTION_ARG(dst, 0); + GET_INSTRUCTION_ARG(src, 1); + + Array *dst_arr = VariantInternal::get_array(dst); + + if (src->get_type() != Variant::ARRAY) { +#ifdef DEBUG_ENABLED + err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + + "' to a variable of type '" + +"'."; +#endif + OPCODE_BREAK; + } + if (!dst_arr->typed_assign(*src)) { +#ifdef DEBUG_ENABLED + err_text = "Trying to assign a typed array with an array of different type.'"; +#endif + OPCODE_BREAK; + } + + ip += 3; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) { CHECK_SPACE(4); GET_INSTRUCTION_ARG(dst, 0); @@ -1308,6 +1377,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } GET_INSTRUCTION_ARG(dst, argc); + *dst = Variant(); // Clear potential previous typed array. *dst = array; @@ -1315,6 +1385,35 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_CONSTRUCT_TYPED_ARRAY) { + CHECK_SPACE(3 + instr_arg_count); + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + + GET_INSTRUCTION_ARG(script_type, argc + 1); + Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 2]; + int native_type_idx = _code_ptr[ip + 3]; + GD_ERR_BREAK(native_type_idx < 0 || native_type_idx >= _global_names_count); + const StringName native_type = _global_names_ptr[native_type_idx]; + + Array array; + array.set_typed(builtin_type, native_type, script_type); + array.resize(argc); + + for (int i = 0; i < argc; i++) { + array[i] = *(instruction_args[i]); + } + + GET_INSTRUCTION_ARG(dst, argc); + *dst = Variant(); // Clear potential previous typed array. + + *dst = array; + + ip += 4; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_CONSTRUCT_DICTIONARY) { CHECK_SPACE(2 + instr_arg_count); @@ -1398,13 +1497,17 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (err.error != Callable::CallError::CALL_OK) { String methodstr = *methodname; String basestr = _get_var_type(base); + bool is_callable = false; if (methodstr == "call") { - if (argc >= 1) { + if (argc >= 1 && base->get_type() != Variant::CALLABLE) { methodstr = String(*argptrs[0]) + " (via call)"; if (err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { err.argument += 1; } + } else { + methodstr = base->operator String() + " (Callable)"; + is_callable = true; } } else if (methodstr == "free") { if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { @@ -1424,7 +1527,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } } } - err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs); + err_text = _get_call_error(err, "function '" + methodstr + (is_callable ? "" : "' in base '" + basestr) + "'", (const Variant **)argptrs); OPCODE_BREAK; } #endif @@ -1513,6 +1616,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_CALL_BUILTIN_STATIC) { + CHECK_SPACE(4 + instr_arg_count); + + ip += instr_arg_count; + + GD_ERR_BREAK(_code_ptr[ip + 1] < 0 || _code_ptr[ip + 1] >= Variant::VARIANT_MAX); + Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 1]; + + int methodname_idx = _code_ptr[ip + 2]; + GD_ERR_BREAK(methodname_idx < 0 || methodname_idx >= _global_names_count); + const StringName *methodname = &_global_names_ptr[methodname_idx]; + + int argc = _code_ptr[ip + 3]; + GD_ERR_BREAK(argc < 0); + + GET_INSTRUCTION_ARG(ret, argc); + + const Variant **argptrs = const_cast<const Variant **>(instruction_args); + +#ifdef DEBUG_ENABLED + uint64_t call_time = 0; + + if (GDScriptLanguage::get_singleton()->profiling) { + call_time = OS::get_singleton()->get_ticks_usec(); + } +#endif + + Callable::CallError err; + Variant::call_static(builtin_type, *methodname, argptrs, argc, *ret, err); + +#ifdef DEBUG_ENABLED + if (GDScriptLanguage::get_singleton()->profiling) { + function_call_time += OS::get_singleton()->get_ticks_usec() - call_time; + } + + if (err.error != Callable::CallError::CALL_OK) { + err_text = _get_call_error(err, "static function '" + methodname->operator String() + "' in type '" + Variant::get_type_name(builtin_type) + "'", argptrs); + OPCODE_BREAK; + } +#endif + + ip += 4; + } + DISPATCH_OPCODE; + #ifdef DEBUG_ENABLED #define OPCODE_CALL_PTR(m_type) \ OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \ @@ -1585,10 +1733,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE_CALL_PTR(VECTOR3I); OPCODE_CALL_PTR(TRANSFORM2D); OPCODE_CALL_PTR(PLANE); - OPCODE_CALL_PTR(QUAT); + OPCODE_CALL_PTR(QUATERNION); OPCODE_CALL_PTR(AABB); OPCODE_CALL_PTR(BASIS); - OPCODE_CALL_PTR(TRANSFORM); + OPCODE_CALL_PTR(TRANSFORM3D); OPCODE_CALL_PTR(COLOR); OPCODE_CALL_PTR(STRING_NAME); OPCODE_CALL_PTR(NODE_PATH); @@ -1822,7 +1970,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED if (err.error != Callable::CallError::CALL_OK) { // TODO: Add this information in debug. - String methodstr = "<unkown function>"; + String methodstr = "<unknown function>"; if (dst->get_type() == Variant::STRING) { // Call provided error string. err_text = "Error calling GDScript utility function '" + methodstr + "': " + String(*dst); @@ -1841,7 +1989,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a ip += instr_arg_count; - int self_fun = _code_ptr[ip + 1]; + int argc = _code_ptr[ip + 1]; + GD_ERR_BREAK(argc < 0); + + int self_fun = _code_ptr[ip + 2]; #ifdef DEBUG_ENABLED if (self_fun < 0 || self_fun >= _global_names_count) { err_text = "compiler bug, function name not found"; @@ -1850,9 +2001,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #endif const StringName *methodname = &_global_names_ptr[self_fun]; - int argc = _code_ptr[ip + 2]; - GD_ERR_BREAK(argc < 0); - Variant **argptrs = instruction_args; GET_INSTRUCTION_ARG(dst, argc); @@ -1951,7 +2099,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a memnew_placement(&gdfs->state.stack.write[sizeof(Variant) * i], Variant(stack[i])); } gdfs->state.stack_size = _stack_size; - gdfs->state.self = self; gdfs->state.alloca_size = alloca_size; gdfs->state.ip = ip + 2; gdfs->state.line = line; @@ -2004,6 +2151,34 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_CREATE_LAMBDA) { + CHECK_SPACE(2 + instr_arg_count); + + ip += instr_arg_count; + + int captures_count = _code_ptr[ip + 1]; + GD_ERR_BREAK(captures_count < 0); + + int lambda_index = _code_ptr[ip + 2]; + GD_ERR_BREAK(lambda_index < 0 || lambda_index >= _lambdas_count); + GDScriptFunction *lambda = _lambdas_ptr[lambda_index]; + + Vector<Variant> captures; + captures.resize(captures_count); + for (int i = 0; i < captures_count; i++) { + GET_INSTRUCTION_ARG(arg, i); + captures.write[i] = *arg; + } + + GDScriptLambdaCallable *callable = memnew(GDScriptLambdaCallable(Ref<GDScript>(script), lambda, captures)); + + GET_INSTRUCTION_ARG(result, captures_count); + *result = Callable(callable); + + ip += 3; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_JUMP) { CHECK_SPACE(2); int to = _code_ptr[ip + 1]; @@ -2063,6 +2238,183 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE_BREAK; } + OPCODE(OPCODE_RETURN_TYPED_BUILTIN) { + CHECK_SPACE(3); + GET_INSTRUCTION_ARG(r, 0); + + Variant::Type ret_type = (Variant::Type)_code_ptr[ip + 2]; + GD_ERR_BREAK(ret_type < 0 || ret_type >= Variant::VARIANT_MAX); + + if (r->get_type() != ret_type) { + if (Variant::can_convert_strict(r->get_type(), ret_type)) { + Callable::CallError ce; + Variant::construct(ret_type, retvalue, const_cast<const Variant **>(&r), 1, ce); + } else { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)", + Variant::get_type_name(r->get_type()), Variant::get_type_name(ret_type)); +#endif // DEBUG_ENABLED + + // Construct a base type anyway so type constraints are met. + Callable::CallError ce; + Variant::construct(ret_type, retvalue, nullptr, 0, ce); + OPCODE_BREAK; + } + } else { + retvalue = *r; + } +#ifdef DEBUG_ENABLED + exit_ok = true; +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + OPCODE(OPCODE_RETURN_TYPED_ARRAY) { + CHECK_SPACE(5); + GET_INSTRUCTION_ARG(r, 0); + + GET_INSTRUCTION_ARG(script_type, 1); + Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 3]; + int native_type_idx = _code_ptr[ip + 4]; + GD_ERR_BREAK(native_type_idx < 0 || native_type_idx >= _global_names_count); + const StringName native_type = _global_names_ptr[native_type_idx]; + + if (r->get_type() != Variant::ARRAY) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "Array[%s]".)", + Variant::get_type_name(r->get_type()), Variant::get_type_name(builtin_type)); +#endif + OPCODE_BREAK; + } + + Array array; + array.set_typed(builtin_type, native_type, script_type); + +#ifdef DEBUG_ENABLED + bool valid = array.typed_assign(*VariantInternal::get_array(r)); +#else + array.typed_assign(*VariantInternal::get_array(r)); +#endif // DEBUG_ENABLED + + // Assign the return value anyway since we want it to be the valid type. + retvalue = array; + +#ifdef DEBUG_ENABLED + if (!valid) { + err_text = "Trying to return a typed array with an array of different type.'"; + OPCODE_BREAK; + } + + exit_ok = true; +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + OPCODE(OPCODE_RETURN_TYPED_NATIVE) { + CHECK_SPACE(3); + GET_INSTRUCTION_ARG(r, 0); + + GET_INSTRUCTION_ARG(type, 1); + GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(type->operator Object *()); + GD_ERR_BREAK(!nc); + + if (r->get_type() != Variant::OBJECT && r->get_type() != Variant::NIL) { + err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)", + Variant::get_type_name(r->get_type()), nc->get_name()); + OPCODE_BREAK; + } + +#ifdef DEBUG_ENABLED + bool freed = false; + Object *ret_obj = r->get_validated_object_with_check(freed); + + if (freed) { + err_text = "Trying to return a previously freed instance."; + OPCODE_BREAK; + } +#else + Object *ret_obj = r->operator Object *(); +#endif // DEBUG_ENABLED + if (ret_obj && !ClassDB::is_parent_class(ret_obj->get_class_name(), nc->get_name())) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)", + ret_obj->get_class_name(), nc->get_name()); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + retvalue = *r; + +#ifdef DEBUG_ENABLED + exit_ok = true; +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + OPCODE(OPCODE_RETURN_TYPED_SCRIPT) { + CHECK_SPACE(3); + GET_INSTRUCTION_ARG(r, 0); + + GET_INSTRUCTION_ARG(type, 1); + Script *base_type = Object::cast_to<Script>(type->operator Object *()); + GD_ERR_BREAK(!base_type); + + if (r->get_type() != Variant::OBJECT && r->get_type() != Variant::NIL) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)", + Variant::get_type_name(r->get_type()), _get_script_name(Ref<Script>(base_type))); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + +#ifdef DEBUG_ENABLED + bool freed = false; + Object *ret_obj = r->get_validated_object_with_check(freed); + + if (freed) { + err_text = "Trying to return a previously freed instance."; + OPCODE_BREAK; + } +#else + Object *ret_obj = r->operator Object *(); +#endif // DEBUG_ENABLED + + if (ret_obj) { + ScriptInstance *ret_inst = ret_obj->get_script_instance(); + if (!ret_inst) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)", + ret_obj->get_class_name(), _get_script_name(Ref<GDScript>(base_type))); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + Script *ret_type = ret_obj->get_script_instance()->get_script().ptr(); + bool valid = false; + + while (ret_type) { + if (ret_type == base_type) { + valid = true; + break; + } + ret_type = ret_type->get_base_script().ptr(); + } + + if (!valid) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)", + _get_script_name(ret_obj->get_script_instance()->get_script()), _get_script_name(Ref<GDScript>(base_type))); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + } + retvalue = *r; + +#ifdef DEBUG_ENABLED + exit_ok = true; +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + OPCODE(OPCODE_ITERATE_BEGIN) { CHECK_SPACE(8); // Space for this and a regular iterate. @@ -2764,6 +3116,63 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_STORE_NAMED_GLOBAL) { + CHECK_SPACE(3); + int globalname_idx = _code_ptr[ip + 2]; + GD_ERR_BREAK(globalname_idx < 0 || globalname_idx >= _global_names_count); + const StringName *globalname = &_global_names_ptr[globalname_idx]; + + GET_INSTRUCTION_ARG(dst, 0); + *dst = GDScriptLanguage::get_singleton()->get_named_globals_map()[*globalname]; + + ip += 3; + } + DISPATCH_OPCODE; + +#define OPCODE_TYPE_ADJUST(m_v_type, m_c_type) \ + OPCODE(OPCODE_TYPE_ADJUST_##m_v_type) { \ + CHECK_SPACE(2); \ + GET_INSTRUCTION_ARG(arg, 0); \ + VariantTypeAdjust<m_c_type>::adjust(arg); \ + ip += 2; \ + } \ + DISPATCH_OPCODE + + OPCODE_TYPE_ADJUST(BOOL, bool); + OPCODE_TYPE_ADJUST(INT, int64_t); + OPCODE_TYPE_ADJUST(FLOAT, double); + OPCODE_TYPE_ADJUST(STRING, String); + OPCODE_TYPE_ADJUST(VECTOR2, Vector2); + OPCODE_TYPE_ADJUST(VECTOR2I, Vector2i); + OPCODE_TYPE_ADJUST(RECT2, Rect2); + OPCODE_TYPE_ADJUST(RECT2I, Rect2i); + OPCODE_TYPE_ADJUST(VECTOR3, Vector3); + OPCODE_TYPE_ADJUST(VECTOR3I, Vector3i); + OPCODE_TYPE_ADJUST(TRANSFORM2D, Transform2D); + OPCODE_TYPE_ADJUST(PLANE, Plane); + OPCODE_TYPE_ADJUST(QUATERNION, Quaternion); + OPCODE_TYPE_ADJUST(AABB, AABB); + OPCODE_TYPE_ADJUST(BASIS, Basis); + OPCODE_TYPE_ADJUST(TRANSFORM, Transform3D); + OPCODE_TYPE_ADJUST(COLOR, Color); + OPCODE_TYPE_ADJUST(STRING_NAME, StringName); + OPCODE_TYPE_ADJUST(NODE_PATH, NodePath); + OPCODE_TYPE_ADJUST(RID, RID); + OPCODE_TYPE_ADJUST(OBJECT, Object *); + OPCODE_TYPE_ADJUST(CALLABLE, Callable); + OPCODE_TYPE_ADJUST(SIGNAL, Signal); + OPCODE_TYPE_ADJUST(DICTIONARY, Dictionary); + OPCODE_TYPE_ADJUST(ARRAY, Array); + OPCODE_TYPE_ADJUST(PACKED_BYTE_ARRAY, PackedByteArray); + OPCODE_TYPE_ADJUST(PACKED_INT32_ARRAY, PackedInt32Array); + OPCODE_TYPE_ADJUST(PACKED_INT64_ARRAY, PackedInt64Array); + OPCODE_TYPE_ADJUST(PACKED_FLOAT32_ARRAY, PackedFloat32Array); + OPCODE_TYPE_ADJUST(PACKED_FLOAT64_ARRAY, PackedFloat64Array); + OPCODE_TYPE_ADJUST(PACKED_STRING_ARRAY, PackedStringArray); + OPCODE_TYPE_ADJUST(PACKED_VECTOR2_ARRAY, PackedVector2Array); + OPCODE_TYPE_ADJUST(PACKED_VECTOR3_ARRAY, PackedVector3Array); + OPCODE_TYPE_ADJUST(PACKED_COLOR_ARRAY, PackedColorArray); + OPCODE(OPCODE_ASSERT) { CHECK_SPACE(3); diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index e63b6ab20e..f817964a3c 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -32,7 +32,6 @@ #include "../gdscript.h" #include "../gdscript_analyzer.h" -#include "core/io/json.h" #include "gdscript_language_protocol.h" #include "gdscript_workspace.h" @@ -49,8 +48,9 @@ void ExtendGDScriptParser::update_diagnostics() { diagnostic.code = -1; lsp::Range range; lsp::Position pos; - int line = LINE_NUMBER_TO_INDEX(error.line); - const String &line_text = get_lines()[line]; + const PackedStringArray lines = get_lines(); + int line = CLAMP(LINE_NUMBER_TO_INDEX(error.line), 0, lines.size() - 1); + const String &line_text = lines[line]; pos.line = line; pos.character = line_text.length() - line_text.strip_edges(true, false).length(); range.start = pos; @@ -182,7 +182,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p symbol.detail += ": " + m.get_datatype().to_string(); } if (m.variable->initializer != nullptr && m.variable->initializer->is_constant) { - symbol.detail += " = " + JSON::print(m.variable->initializer->reduced_value); + symbol.detail += " = " + m.variable->initializer->reduced_value.to_json_string(); } symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.variable->start_line)); @@ -223,10 +223,10 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p } } } else { - value_text = JSON::print(default_value); + value_text = default_value.to_json_string(); } } else { - value_text = JSON::print(default_value); + value_text = default_value.to_json_string(); } if (!value_text.is_empty()) { symbol.detail += " = " + value_text; @@ -352,8 +352,7 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN parameters += ": " + parameter->get_datatype().to_string(); } if (parameter->default_value != nullptr) { - String value = JSON::print(parameter->default_value->reduced_value); - parameters += " = " + value; + parameters += " = " + parameter->default_value->reduced_value.to_json_string(); } } r_symbol.detail += parameters + ")"; @@ -361,24 +360,73 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN r_symbol.detail += " -> " + p_func->get_datatype().to_string(); } - for (int i = 0; i < p_func->body->locals.size(); i++) { - const SuiteNode::Local &local = p_func->body->locals[i]; - lsp::DocumentSymbol symbol; - symbol.name = local.name; - symbol.kind = local.type == SuiteNode::Local::CONSTANT ? lsp::SymbolKind::Constant : lsp::SymbolKind::Variable; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(local.start_line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(local.start_column); - symbol.range.end.line = LINE_NUMBER_TO_INDEX(local.end_line); - symbol.range.end.character = LINE_NUMBER_TO_INDEX(local.end_column); - symbol.uri = uri; - symbol.script_path = path; - symbol.detail = SuiteNode::Local::CONSTANT ? "const " : "var "; - symbol.detail += symbol.name; - if (local.get_datatype().is_hard_type()) { - symbol.detail += ": " + local.get_datatype().to_string(); + List<GDScriptParser::SuiteNode *> function_nodes; + + List<GDScriptParser::Node *> node_stack; + node_stack.push_back(p_func->body); + + while (!node_stack.is_empty()) { + GDScriptParser::Node *node = node_stack[0]; + node_stack.pop_front(); + + switch (node->type) { + case GDScriptParser::TypeNode::IF: { + GDScriptParser::IfNode *if_node = (GDScriptParser::IfNode *)node; + node_stack.push_back(if_node->true_block); + if (if_node->false_block) { + node_stack.push_back(if_node->false_block); + } + } break; + + case GDScriptParser::TypeNode::FOR: { + GDScriptParser::ForNode *for_node = (GDScriptParser::ForNode *)node; + node_stack.push_back(for_node->loop); + } break; + + case GDScriptParser::TypeNode::WHILE: { + GDScriptParser::WhileNode *while_node = (GDScriptParser::WhileNode *)node; + node_stack.push_back(while_node->loop); + } break; + + case GDScriptParser::TypeNode::MATCH_BRANCH: { + GDScriptParser::MatchBranchNode *match_node = (GDScriptParser::MatchBranchNode *)node; + node_stack.push_back(match_node->block); + } break; + + case GDScriptParser::TypeNode::SUITE: { + GDScriptParser::SuiteNode *suite_node = (GDScriptParser::SuiteNode *)node; + function_nodes.push_back(suite_node); + for (int i = 0; i < suite_node->statements.size(); ++i) { + node_stack.push_back(suite_node->statements[i]); + } + } break; + + default: + continue; + } + } + + for (List<GDScriptParser::SuiteNode *>::Element *N = function_nodes.front(); N; N = N->next()) { + const GDScriptParser::SuiteNode *suite_node = N->get(); + for (int i = 0; i < suite_node->locals.size(); i++) { + const SuiteNode::Local &local = suite_node->locals[i]; + lsp::DocumentSymbol symbol; + symbol.name = local.name; + symbol.kind = local.type == SuiteNode::Local::CONSTANT ? lsp::SymbolKind::Constant : lsp::SymbolKind::Variable; + symbol.range.start.line = LINE_NUMBER_TO_INDEX(local.start_line); + symbol.range.start.character = LINE_NUMBER_TO_INDEX(local.start_column); + symbol.range.end.line = LINE_NUMBER_TO_INDEX(local.end_line); + symbol.range.end.character = LINE_NUMBER_TO_INDEX(local.end_column); + symbol.uri = uri; + symbol.script_path = path; + symbol.detail = local.type == SuiteNode::Local::CONSTANT ? "const " : "var "; + symbol.detail += symbol.name; + if (local.get_datatype().is_hard_type()) { + symbol.detail += ": " + local.get_datatype().to_string(); + } + symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(local.start_line)); + r_symbol.children.push_back(symbol); } - symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(local.start_line)); - r_symbol.children.push_back(symbol); } } diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 912c9a174e..0d1f98778e 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -31,8 +31,6 @@ #include "gdscript_language_protocol.h" #include "core/config/project_settings.h" -#include "core/io/json.h" -#include "core/os/copymem.h" #include "editor/doc_tools.h" #include "editor/editor_log.h" #include "editor/editor_node.h" @@ -195,7 +193,7 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { vformat("GDScriptLanguageProtocol: Can't initialize invalid peer '%d'.", latest_client_id)); Ref<LSPeer> peer = clients.get(latest_client_id); if (peer != nullptr) { - String msg = JSON::print(request); + String msg = Variant(request).to_json_string(); msg = format_output(msg); (*peer)->res_queue.push_back(msg.utf8()); } @@ -256,7 +254,7 @@ void GDScriptLanguageProtocol::poll() { } } -Error GDScriptLanguageProtocol::start(int p_port, const IP_Address &p_bind_ip) { +Error GDScriptLanguageProtocol::start(int p_port, const IPAddress &p_bind_ip) { return server->listen(p_port, p_bind_ip); } @@ -281,7 +279,7 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia ERR_FAIL_COND(peer == nullptr); Dictionary message = make_notification(p_method, p_params); - String msg = JSON::print(message); + String msg = Variant(message).to_json_string(); msg = format_output(msg); peer->res_queue.push_back(msg.utf8()); } @@ -295,10 +293,10 @@ bool GDScriptLanguageProtocol::is_goto_native_symbols_enabled() const { } GDScriptLanguageProtocol::GDScriptLanguageProtocol() { - server.instance(); + server.instantiate(); singleton = this; - workspace.instance(); - text_document.instance(); + workspace.instantiate(); + text_document.instantiate(); set_scope("textDocument", text_document.ptr()); set_scope("completionItem", text_document.ptr()); set_scope("workspace", workspace.ptr()); diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h index 8b08ae0655..5a2dd55c46 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.h +++ b/modules/gdscript/language_server/gdscript_language_protocol.h @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef GDSCRIPT_PROTOCAL_SERVER_H -#define GDSCRIPT_PROTOCAL_SERVER_H +#ifndef GDSCRIPT_LANGUAGE_PROTOCOL_H +#define GDSCRIPT_LANGUAGE_PROTOCOL_H #include "core/io/stream_peer.h" #include "core/io/stream_peer_tcp.h" @@ -37,7 +37,13 @@ #include "gdscript_text_document.h" #include "gdscript_workspace.h" #include "lsp.hpp" + +#include "modules/modules_enabled.gen.h" +#ifdef MODULE_JSONRPC_ENABLED #include "modules/jsonrpc/jsonrpc.h" +#else +#error "Can't build GDScript LSP without JSONRPC module." +#endif #define LSP_MAX_BUFFER_SIZE 4194304 #define LSP_MAX_CLIENTS 8 @@ -46,7 +52,7 @@ class GDScriptLanguageProtocol : public JSONRPC { GDCLASS(GDScriptLanguageProtocol, JSONRPC) private: - struct LSPeer : Reference { + struct LSPeer : RefCounted { Ref<StreamPeerTCP> connection; uint8_t req_buf[LSP_MAX_BUFFER_SIZE]; @@ -69,7 +75,7 @@ private: static GDScriptLanguageProtocol *singleton; HashMap<int, Ref<LSPeer>> clients; - Ref<TCP_Server> server; + Ref<TCPServer> server; int latest_client_id = 0; int next_client_id = 0; @@ -97,7 +103,7 @@ public: _FORCE_INLINE_ bool is_initialized() const { return _initialized; } void poll(); - Error start(int p_port, const IP_Address &p_bind_ip); + Error start(int p_port, const IPAddress &p_bind_ip); void stop(); void notify_client(const String &p_method, const Variant &p_params = Variant(), int p_client_id = -1); @@ -108,4 +114,4 @@ public: GDScriptLanguageProtocol(); }; -#endif +#endif // GDSCRIPT_LANGUAGE_PROTOCOL_H diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp index 98ada9de4d..c47164d95b 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -30,7 +30,7 @@ #include "gdscript_language_server.h" -#include "core/os/file_access.h" +#include "core/io/file_access.h" #include "core/os/os.h" #include "editor/editor_log.h" #include "editor/editor_node.h" @@ -78,7 +78,7 @@ void GDScriptLanguageServer::thread_main(void *p_userdata) { void GDScriptLanguageServer::start() { port = (int)_EDITOR_GET("network/language_server/remote_port"); use_thread = (bool)_EDITOR_GET("network/language_server/use_thread"); - if (protocol.start(port, IP_Address("127.0.0.1")) == OK) { + if (protocol.start(port, IPAddress("127.0.0.1")) == OK) { EditorNode::get_log()->add_message("--- GDScript language server started ---", EditorLog::MSG_TYPE_EDITOR); if (use_thread) { thread_running = true; @@ -101,7 +101,7 @@ void GDScriptLanguageServer::stop() { } void register_lsp_types() { - ClassDB::register_class<GDScriptLanguageProtocol>(); - ClassDB::register_class<GDScriptTextDocument>(); - ClassDB::register_class<GDScriptWorkspace>(); + GDREGISTER_CLASS(GDScriptLanguageProtocol); + GDREGISTER_CLASS(GDScriptTextDocument); + GDREGISTER_CLASS(GDScriptWorkspace); } diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index 9f2373bf56..59d2e6c8fa 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -40,6 +40,7 @@ void GDScriptTextDocument::_bind_methods() { ClassDB::bind_method(D_METHOD("didOpen"), &GDScriptTextDocument::didOpen); + ClassDB::bind_method(D_METHOD("didClose"), &GDScriptTextDocument::didClose); ClassDB::bind_method(D_METHOD("didChange"), &GDScriptTextDocument::didChange); ClassDB::bind_method(D_METHOD("nativeSymbol"), &GDScriptTextDocument::nativeSymbol); ClassDB::bind_method(D_METHOD("documentSymbol"), &GDScriptTextDocument::documentSymbol); @@ -61,6 +62,11 @@ void GDScriptTextDocument::didOpen(const Variant &p_param) { sync_script_content(doc.uri, doc.text); } +void GDScriptTextDocument::didClose(const Variant &p_param) { + // Left empty on purpose. Godot does nothing special on closing a document, + // but it satisfies LSP clients that require didClose be implemented. +} + void GDScriptTextDocument::didChange(const Variant &p_param) { lsp::TextDocumentItem doc = load_document_item(p_param); Dictionary dict = p_param; @@ -400,6 +406,7 @@ GDScriptTextDocument::~GDScriptTextDocument() { void GDScriptTextDocument::sync_script_content(const String &p_path, const String &p_content) { String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_path); GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content); + EditorFileSystem::get_singleton()->update_file(path); } void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) { diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h index 792e601bc1..e2987f779c 100644 --- a/modules/gdscript/language_server/gdscript_text_document.h +++ b/modules/gdscript/language_server/gdscript_text_document.h @@ -31,18 +31,19 @@ #ifndef GDSCRIPT_TEXT_DOCUMENT_H #define GDSCRIPT_TEXT_DOCUMENT_H -#include "core/object/reference.h" -#include "core/os/file_access.h" +#include "core/io/file_access.h" +#include "core/object/ref_counted.h" #include "lsp.hpp" -class GDScriptTextDocument : public Reference { - GDCLASS(GDScriptTextDocument, Reference) +class GDScriptTextDocument : public RefCounted { + GDCLASS(GDScriptTextDocument, RefCounted) protected: static void _bind_methods(); FileAccess *file_checker; void didOpen(const Variant &p_param); + void didClose(const Variant &p_param); void didChange(const Variant &p_param); void sync_script_content(const String &p_path, const String &p_content); diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 69cad1a335..1915c92cbf 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -42,6 +42,7 @@ #include "scene/resources/packed_scene.h" void GDScriptWorkspace::_bind_methods() { + ClassDB::bind_method(D_METHOD("didDeleteFiles"), &GDScriptWorkspace::did_delete_files); ClassDB::bind_method(D_METHOD("symbol"), &GDScriptWorkspace::symbol); ClassDB::bind_method(D_METHOD("parse_script", "path", "content"), &GDScriptWorkspace::parse_script); ClassDB::bind_method(D_METHOD("parse_local_script", "path"), &GDScriptWorkspace::parse_local_script); @@ -51,6 +52,16 @@ void GDScriptWorkspace::_bind_methods() { ClassDB::bind_method(D_METHOD("generate_script_api", "path"), &GDScriptWorkspace::generate_script_api); } +void GDScriptWorkspace::did_delete_files(const Dictionary &p_params) { + Array files = p_params["files"]; + for (int i = 0; i < files.size(); ++i) { + Dictionary file = files[i]; + String uri = file["uri"]; + String path = get_file_path(uri); + parse_script(path, ""); + } +} + void GDScriptWorkspace::remove_cache_parser(const String &p_path) { Map<String, ExtendGDScriptParser *>::Element *parser = parse_results.find(p_path); Map<String, ExtendGDScriptParser *>::Element *script = scripts.find(p_path); @@ -177,7 +188,9 @@ Array GDScriptWorkspace::symbol(const Dictionary &p_params) { E->get()->get_symbols().symbol_tree_as_list(E->key(), script_symbols); for (int i = 0; i < script_symbols.size(); ++i) { if (query.is_subsequence_ofi(script_symbols[i].name)) { - arr.push_back(script_symbols[i].to_json()); + lsp::DocumentedSymbolInformation symbol = script_symbols[i]; + symbol.location.uri = get_file_uri(symbol.location.uri); + arr.push_back(symbol.to_json()); } } } @@ -413,7 +426,7 @@ Node *GDScriptWorkspace::_get_owner_scene_node(String p_path) { RES owner_res = ResourceLoader::load(owner_path); if (Object::cast_to<PackedScene>(owner_res.ptr())) { Ref<PackedScene> owner_packed_scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*owner_res)); - owner_scene_node = owner_packed_scene->instance(); + owner_scene_node = owner_packed_scene->instantiate(); break; } } diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h index 7fd8bfcf20..8b166a873c 100644 --- a/modules/gdscript/language_server/gdscript_workspace.h +++ b/modules/gdscript/language_server/gdscript_workspace.h @@ -37,8 +37,8 @@ #include "gdscript_extend_parser.h" #include "lsp.hpp" -class GDScriptWorkspace : public Reference { - GDCLASS(GDScriptWorkspace, Reference); +class GDScriptWorkspace : public RefCounted { + GDCLASS(GDScriptWorkspace, RefCounted); private: void _get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners); @@ -89,6 +89,7 @@ public: void resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list); Dictionary generate_script_api(const String &p_path); Error resolve_signature(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::SignatureHelp &r_signature); + void did_delete_files(const Dictionary &p_params); GDScriptWorkspace(); ~GDScriptWorkspace(); diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index 6635098be2..a7dcfdb22d 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -766,7 +766,7 @@ struct MarkupContent { // Use namespace instead of enumeration to follow the LSP specifications // lsp::EnumName::EnumValue is OK but lsp::EnumValue is not -// And here C++ compilers are unhappy with our enumeration name like Color, File, Reference etc. +// And here C++ compilers are unhappy with our enumeration name like Color, File, RefCounted etc. /** * The kind of a completion entry. */ @@ -788,7 +788,7 @@ static const int Keyword = 14; static const int Snippet = 15; static const int Color = 16; static const int File = 17; -static const int Reference = 18; +static const int RefCounted = 18; static const int Folder = 19; static const int EnumMember = 20; static const int Constant = 21; @@ -1528,6 +1528,114 @@ struct SignatureHelp { } }; +/** + * A pattern to describe in which file operation requests or notifications + * the server is interested in. + */ +struct FileOperationPattern { + /** + * The glob pattern to match. + */ + String glob = "**/*.gd"; + + /** + * Whether to match `file`s or `folder`s with this pattern. + * + * Matches both if undefined. + */ + String matches = "file"; + + Dictionary to_json() const { + Dictionary dict; + + dict["glob"] = glob; + dict["matches"] = matches; + + return dict; + } +}; + +/** + * A filter to describe in which file operation requests or notifications + * the server is interested in. + */ +struct FileOperationFilter { + /** + * The actual file operation pattern. + */ + FileOperationPattern pattern; + + Dictionary to_json() const { + Dictionary dict; + + dict["pattern"] = pattern.to_json(); + + return dict; + } +}; + +/** + * The options to register for file operations. + */ +struct FileOperationRegistrationOptions { + /** + * The actual filters. + */ + Vector<FileOperationFilter> filters; + + FileOperationRegistrationOptions() { + filters.push_back(FileOperationFilter()); + } + + Dictionary to_json() const { + Dictionary dict; + + Array filts; + for (int i = 0; i < filters.size(); i++) { + filts.push_back(filters[i].to_json()); + } + dict["filters"] = filts; + + return dict; + } +}; + +/** + * The server is interested in file notifications/requests. + */ +struct FileOperations { + /** + * The server is interested in receiving didDeleteFiles file notifications. + */ + FileOperationRegistrationOptions didDelete; + + Dictionary to_json() const { + Dictionary dict; + + dict["didDelete"] = didDelete.to_json(); + + return dict; + } +}; + +/** + * Workspace specific server capabilities + */ +struct Workspace { + /** + * The server is interested in file notifications/requests. + */ + FileOperations fileOperations; + + Dictionary to_json() const { + Dictionary dict; + + dict["fileOperations"] = fileOperations.to_json(); + + return dict; + } +}; + struct ServerCapabilities { /** * Defines how text documents are synced. Is either a detailed structure defining each notification or @@ -1590,6 +1698,11 @@ struct ServerCapabilities { bool workspaceSymbolProvider = true; /** + * The server supports workspace folder. + */ + Workspace workspace; + + /** * The server provides code actions. The `CodeActionOptions` return type is only * valid if the client signals code action literal support via the property * `textDocument.codeAction.codeActionLiteralSupport`. @@ -1676,6 +1789,7 @@ struct ServerCapabilities { dict["documentHighlightProvider"] = documentHighlightProvider; dict["documentSymbolProvider"] = documentSymbolProvider; dict["workspaceSymbolProvider"] = workspaceSymbolProvider; + dict["workspace"] = workspace.to_json(); dict["codeActionProvider"] = codeActionProvider; dict["documentFormattingProvider"] = documentFormattingProvider; dict["documentRangeFormattingProvider"] = documentRangeFormattingProvider; diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index e90475a60e..c2b1981f31 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -30,10 +30,10 @@ #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 "core/os/dir_access.h" -#include "core/os/file_access.h" #include "gdscript.h" #include "gdscript_analyzer.h" #include "gdscript_cache.h" @@ -85,19 +85,19 @@ public: return; } - // TODO: Readd compiled GDScript on export. + // TODO: Re-add compiled GDScript on export. return; } }; static void _editor_init() { Ref<EditorExportGDScript> gd_export; - gd_export.instance(); + gd_export.instantiate(); EditorExport::get_singleton()->add_export_plugin(gd_export); #ifdef TOOLS_ENABLED Ref<GDScriptSyntaxHighlighter> gdscript_syntax_highlighter; - gdscript_syntax_highlighter.instance(); + gdscript_syntax_highlighter.instantiate(); ScriptEditor::get_singleton()->register_syntax_highlighter(gdscript_syntax_highlighter); #endif @@ -112,15 +112,15 @@ static void _editor_init() { #endif // TOOLS_ENABLED void register_gdscript_types() { - ClassDB::register_class<GDScript>(); + GDREGISTER_CLASS(GDScript); script_language_gd = memnew(GDScriptLanguage); ScriptServer::register_language(script_language_gd); - resource_loader_gd.instance(); + resource_loader_gd.instantiate(); ResourceLoader::add_resource_format_loader(resource_loader_gd); - resource_saver_gd.instance(); + resource_saver_gd.instantiate(); ResourceSaver::add_resource_format_saver(resource_saver_gd); gdscript_cache = memnew(GDScriptCache); @@ -128,7 +128,7 @@ void register_gdscript_types() { #ifdef TOOLS_ENABLED EditorNode::add_init_callback(_editor_init); - gdscript_translation_parser_plugin.instance(); + gdscript_translation_parser_plugin.instantiate(); EditorTranslationParser::get_singleton()->add_parser(gdscript_translation_parser_plugin, EditorTranslationParser::STANDARD); #endif // TOOLS_ENABLED @@ -158,25 +158,24 @@ void unregister_gdscript_types() { #endif // TOOLS_ENABLED GDScriptParser::cleanup(); - GDScriptAnalyzer::cleanup(); GDScriptUtilityFunctions::unregister_functions(); } #ifdef TESTS_ENABLED void test_tokenizer() { - TestGDScript::test(TestGDScript::TestType::TEST_TOKENIZER); + GDScriptTests::test(GDScriptTests::TestType::TEST_TOKENIZER); } void test_parser() { - TestGDScript::test(TestGDScript::TestType::TEST_PARSER); + GDScriptTests::test(GDScriptTests::TestType::TEST_PARSER); } void test_compiler() { - TestGDScript::test(TestGDScript::TestType::TEST_COMPILER); + GDScriptTests::test(GDScriptTests::TestType::TEST_COMPILER); } void test_bytecode() { - TestGDScript::test(TestGDScript::TestType::TEST_BYTECODE); + GDScriptTests::test(GDScriptTests::TestType::TEST_BYTECODE); } REGISTER_TEST_COMMAND("gdscript-tokenizer", &test_tokenizer); diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp new file mode 100644 index 0000000000..b7faebb4ef --- /dev/null +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -0,0 +1,583 @@ +/*************************************************************************/ +/* gdscript_test_runner.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gdscript_test_runner.h" + +#include "../gdscript.h" +#include "../gdscript_analyzer.h" +#include "../gdscript_compiler.h" +#include "../gdscript_parser.h" + +#include "core/config/project_settings.h" +#include "core/core_string_names.h" +#include "core/io/dir_access.h" +#include "core/io/file_access_pack.h" +#include "core/os/os.h" +#include "core/string/string_builder.h" +#include "scene/resources/packed_scene.h" + +#include "tests/test_macros.h" + +namespace GDScriptTests { + +void init_autoloads() { + Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + + // First pass, add the constants so they exist before any script is loaded. + for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { + const ProjectSettings::AutoloadInfo &info = E->get(); + + if (info.is_singleton) { + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->add_global_constant(info.name, Variant()); + } + } + } + + // Second pass, load into global constants. + for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { + const ProjectSettings::AutoloadInfo &info = E->get(); + + if (!info.is_singleton) { + // Skip non-singletons since we don't have a scene tree here anyway. + continue; + } + + RES res = ResourceLoader::load(info.path); + ERR_CONTINUE_MSG(res.is_null(), "Can't autoload: " + info.path); + Node *n = nullptr; + if (res->is_class("PackedScene")) { + Ref<PackedScene> ps = res; + n = ps->instantiate(); + } else if (res->is_class("Script")) { + Ref<Script> script_res = res; + StringName ibt = script_res->get_instance_base_type(); + bool valid_type = ClassDB::is_parent_class(ibt, "Node"); + ERR_CONTINUE_MSG(!valid_type, "Script does not inherit a Node: " + info.path); + + Object *obj = ClassDB::instantiate(ibt); + + ERR_CONTINUE_MSG(obj == nullptr, + "Cannot instance script for autoload, expected 'Node' inheritance, got: " + + String(ibt)); + + n = Object::cast_to<Node>(obj); + n->set_script(script_res); + } + + ERR_CONTINUE_MSG(!n, "Path in autoload not a node or script: " + info.path); + n->set_name(info.name); + + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->add_global_constant(info.name, n); + } + } +} + +void init_language(const String &p_base_path) { + // Setup project settings since it's needed by the languages to get the global scripts. + // This also sets up the base resource path. + Error err = ProjectSettings::get_singleton()->setup(p_base_path, String(), true); + if (err) { + print_line("Could not load project settings."); + // Keep going since some scripts still work without this. + } + + // Initialize the language for the test routine. + GDScriptLanguage::get_singleton()->init(); + init_autoloads(); +} + +void finish_language() { + GDScriptLanguage::get_singleton()->finish(); + ScriptServer::global_classes_clear(); +} + +StringName GDScriptTestRunner::test_function_name; + +GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_language) { + test_function_name = StaticCString::create("test"); + do_init_languages = p_init_language; + + source_dir = p_source_dir; + if (!source_dir.ends_with("/")) { + source_dir += "/"; + } + + if (do_init_languages) { + init_language(p_source_dir); + + // Enable all warnings for GDScript, so we can test them. + ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true); + for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) { + String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower(); + ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/" + warning, true); + } + } + + // Enable printing to show results + _print_line_enabled = true; + _print_error_enabled = true; +} + +GDScriptTestRunner::~GDScriptTestRunner() { + test_function_name = StringName(); + if (do_init_languages) { + finish_language(); + } +} + +int GDScriptTestRunner::run_tests() { + if (!make_tests()) { + FAIL("An error occurred while making the tests."); + return -1; + } + + if (!generate_class_index()) { + FAIL("An error occurred while generating class index."); + return -1; + } + + int failed = 0; + for (int i = 0; i < tests.size(); i++) { + GDScriptTest test = tests[i]; + GDScriptTest::TestResult result = test.run_test(); + + String expected = FileAccess::get_file_as_string(test.get_output_file()); + INFO(test.get_source_file()); + if (!result.passed) { + INFO(expected); + failed++; + } + + CHECK_MESSAGE(result.passed, (result.passed ? String() : result.output)); + } + + return failed; +} + +bool GDScriptTestRunner::generate_outputs() { + is_generating = true; + + if (!make_tests()) { + print_line("Failed to generate a test output."); + return false; + } + + if (!generate_class_index()) { + return false; + } + + for (int i = 0; i < tests.size(); i++) { + OS::get_singleton()->print("."); + GDScriptTest test = tests[i]; + bool result = test.generate_output(); + + if (!result) { + print_line("\nCould not generate output for " + test.get_source_file()); + return false; + } + } + print_line("\nGenerated output files for " + itos(tests.size()) + " tests successfully."); + + return true; +} + +bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) { + Error err = OK; + DirAccessRef dir(DirAccess::open(p_dir, &err)); + + if (err != OK) { + return false; + } + + String current_dir = dir->get_current_dir(); + + dir->list_dir_begin(); + String next = dir->get_next(); + + while (!next.is_empty()) { + if (dir->current_is_dir()) { + if (next == "." || next == "..") { + next = dir->get_next(); + continue; + } + if (!make_tests_for_dir(current_dir.plus_file(next))) { + return false; + } + } else { + if (next.get_extension().to_lower() == "gd") { + String out_file = next.get_basename() + ".out"; + if (!is_generating && !dir->file_exists(out_file)) { + ERR_FAIL_V_MSG(false, "Could not find output file for " + next); + } + GDScriptTest test(current_dir.plus_file(next), current_dir.plus_file(out_file), source_dir); + tests.push_back(test); + } + } + + next = dir->get_next(); + } + + dir->list_dir_end(); + + return true; +} + +bool GDScriptTestRunner::make_tests() { + Error err = OK; + DirAccessRef dir(DirAccess::open(source_dir, &err)); + + ERR_FAIL_COND_V_MSG(err != OK, false, "Could not open specified test directory."); + + source_dir = dir->get_current_dir() + "/"; // Make it absolute path. + return make_tests_for_dir(dir->get_current_dir()); +} + +bool GDScriptTestRunner::generate_class_index() { + StringName gdscript_name = GDScriptLanguage::get_singleton()->get_name(); + for (int i = 0; i < tests.size(); i++) { + GDScriptTest test = tests[i]; + String base_type; + + String class_name = GDScriptLanguage::get_singleton()->get_global_class_name(test.get_source_file(), &base_type); + if (class_name == String()) { + continue; + } + ERR_FAIL_COND_V_MSG(ScriptServer::is_global_class(class_name), false, + "Class name '" + class_name + "' from " + test.get_source_file() + " is already used in " + ScriptServer::get_global_class_path(class_name)); + + ScriptServer::add_global_class(class_name, base_type, gdscript_name, test.get_source_file()); + } + return true; +} + +GDScriptTest::GDScriptTest(const String &p_source_path, const String &p_output_path, const String &p_base_dir) { + source_file = p_source_path; + output_file = p_output_path; + base_dir = p_base_dir; + _print_handler.printfunc = print_handler; + _error_handler.errfunc = error_handler; +} + +void GDScriptTestRunner::handle_cmdline() { + List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); + // TODO: this could likely be ported to use test commands: + // https://github.com/godotengine/godot/pull/41355 + // Currently requires to startup the whole engine, which is slow. + String test_cmd = "--gdscript-test"; + String gen_cmd = "--gdscript-generate-tests"; + + for (List<String>::Element *E = cmdline_args.front(); E != nullptr; E = E->next()) { + String &cmd = E->get(); + if (cmd == test_cmd || cmd == gen_cmd) { + if (E->next() == nullptr) { + ERR_PRINT("Needed a path for the test files."); + exit(-1); + } + + const String &path = E->next()->get(); + + GDScriptTestRunner runner(path, false); + int failed = 0; + if (cmd == test_cmd) { + failed = runner.run_tests(); + } else { + bool completed = runner.generate_outputs(); + failed = completed ? 0 : -1; + } + exit(failed); + } + } +} + +void GDScriptTest::enable_stdout() { + // TODO: this could likely be handled by doctest or `tests/test_macros.h`. + OS::get_singleton()->set_stdout_enabled(true); + OS::get_singleton()->set_stderr_enabled(true); +} + +void GDScriptTest::disable_stdout() { + // TODO: this could likely be handled by doctest or `tests/test_macros.h`. + OS::get_singleton()->set_stdout_enabled(false); + OS::get_singleton()->set_stderr_enabled(false); +} + +void GDScriptTest::print_handler(void *p_this, const String &p_message, bool p_error) { + TestResult *result = (TestResult *)p_this; + result->output += p_message + "\n"; +} + +void GDScriptTest::error_handler(void *p_this, const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_explanation, ErrorHandlerType p_type) { + ErrorHandlerData *data = (ErrorHandlerData *)p_this; + GDScriptTest *self = data->self; + TestResult *result = data->result; + + result->status = GDTEST_RUNTIME_ERROR; + + StringBuilder builder; + builder.append(">> "); + switch (p_type) { + case ERR_HANDLER_ERROR: + builder.append("ERROR"); + break; + case ERR_HANDLER_WARNING: + builder.append("WARNING"); + break; + case ERR_HANDLER_SCRIPT: + builder.append("SCRIPT ERROR"); + break; + case ERR_HANDLER_SHADER: + builder.append("SHADER ERROR"); + break; + default: + builder.append("Unknown error type"); + break; + } + + builder.append("\n>> on function: "); + builder.append(p_function); + builder.append("()\n>> "); + builder.append(String(p_file).trim_prefix(self->base_dir)); + builder.append("\n>> "); + builder.append(itos(p_line)); + builder.append("\n>> "); + builder.append(p_error); + if (strlen(p_explanation) > 0) { + builder.append("\n>> "); + builder.append(p_explanation); + } + builder.append("\n"); + + result->output = builder.as_string(); +} + +bool GDScriptTest::check_output(const String &p_output) const { + Error err = OK; + String expected = FileAccess::get_file_as_string(output_file, &err); + + ERR_FAIL_COND_V_MSG(err != OK, false, "Error when opening the output file."); + + String got = p_output.strip_edges(); // TODO: may be hacky. + got += "\n"; // Make sure to insert newline for CI static checks. + + return got == expected; +} + +String GDScriptTest::get_text_for_status(GDScriptTest::TestStatus p_status) const { + switch (p_status) { + case GDTEST_OK: + return "GDTEST_OK"; + case GDTEST_LOAD_ERROR: + return "GDTEST_LOAD_ERROR"; + case GDTEST_PARSER_ERROR: + return "GDTEST_PARSER_ERROR"; + case GDTEST_ANALYZER_ERROR: + return "GDTEST_ANALYZER_ERROR"; + case GDTEST_COMPILER_ERROR: + return "GDTEST_COMPILER_ERROR"; + case GDTEST_RUNTIME_ERROR: + return "GDTEST_RUNTIME_ERROR"; + } + return ""; +} + +GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { + disable_stdout(); + + TestResult result; + result.status = GDTEST_OK; + result.output = String(); + + Error err = OK; + + // Create script. + Ref<GDScript> script; + script.instantiate(); + script->set_path(source_file); + script->set_script_path(source_file); + err = script->load_source_code(source_file); + if (err != OK) { + enable_stdout(); + result.status = GDTEST_LOAD_ERROR; + result.passed = false; + ERR_FAIL_V_MSG(result, "\nCould not load source code for: '" + source_file + "'"); + } + + // Test parsing. + GDScriptParser parser; + err = parser.parse(script->get_source_code(), source_file, false); + if (err != OK) { + enable_stdout(); + result.status = GDTEST_PARSER_ERROR; + result.output = get_text_for_status(result.status) + "\n"; + + const List<GDScriptParser::ParserError> &errors = parser.get_errors(); + for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E; E = E->next()) { + result.output += E->get().message + "\n"; // TODO: line, column? + break; // Only the first error since the following might be cascading. + } + if (!p_is_generating) { + result.passed = check_output(result.output); + } + return result; + } + + // Test type-checking. + GDScriptAnalyzer analyzer(&parser); + err = analyzer.analyze(); + if (err != OK) { + enable_stdout(); + result.status = GDTEST_ANALYZER_ERROR; + result.output = get_text_for_status(result.status) + "\n"; + + const List<GDScriptParser::ParserError> &errors = parser.get_errors(); + for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E; E = E->next()) { + result.output += E->get().message + "\n"; // TODO: line, column? + break; // Only the first error since the following might be cascading. + } + if (!p_is_generating) { + result.passed = check_output(result.output); + } + return result; + } + + StringBuilder warning_string; + for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E != nullptr; E = E->next()) { + const GDScriptWarning warning = E->get(); + warning_string.append(">> WARNING"); + warning_string.append("\n>> Line: "); + warning_string.append(itos(warning.start_line)); + warning_string.append("\n>> "); + warning_string.append(warning.get_name()); + warning_string.append("\n>> "); + warning_string.append(warning.get_message()); + warning_string.append("\n"); + } + result.output += warning_string.as_string(); + + // Test compiling. + GDScriptCompiler compiler; + err = compiler.compile(&parser, script.ptr(), false); + if (err != OK) { + enable_stdout(); + result.status = GDTEST_COMPILER_ERROR; + result.output = get_text_for_status(result.status) + "\n"; + result.output = compiler.get_error(); + if (!p_is_generating) { + result.passed = check_output(result.output); + } + return result; + } + + // Test running. + const Map<StringName, GDScriptFunction *>::Element *test_function_element = script->get_member_functions().find(GDScriptTestRunner::test_function_name); + if (test_function_element == nullptr) { + enable_stdout(); + result.status = GDTEST_LOAD_ERROR; + result.output = ""; + result.passed = false; + ERR_FAIL_V_MSG(result, "\nCould not find test function on: '" + source_file + "'"); + } + + script->reload(); + + // Create object instance for test. + Object *obj = ClassDB::instantiate(script->get_native()->get_name()); + Ref<RefCounted> obj_ref; + if (obj->is_ref_counted()) { + obj_ref = Ref<RefCounted>(Object::cast_to<RefCounted>(obj)); + } + obj->set_script(script); + GDScriptInstance *instance = static_cast<GDScriptInstance *>(obj->get_script_instance()); + + // Setup output handlers. + ErrorHandlerData error_data(&result, this); + + _print_handler.userdata = &result; + _error_handler.userdata = &error_data; + add_print_handler(&_print_handler); + add_error_handler(&_error_handler); + + // Call test function. + Callable::CallError call_err; + instance->call(GDScriptTestRunner::test_function_name, nullptr, 0, call_err); + + // Tear down output handlers. + remove_print_handler(&_print_handler); + remove_error_handler(&_error_handler); + + // Check results. + if (call_err.error != Callable::CallError::CALL_OK) { + enable_stdout(); + result.status = GDTEST_LOAD_ERROR; + result.passed = false; + ERR_FAIL_V_MSG(result, "\nCould not call test function on: '" + source_file + "'"); + } + + result.output = get_text_for_status(result.status) + "\n" + result.output; + if (!p_is_generating) { + result.passed = check_output(result.output); + } + + if (obj_ref.is_null()) { + memdelete(obj); + } + + enable_stdout(); + return result; +} + +GDScriptTest::TestResult GDScriptTest::run_test() { + return execute_test_code(false); +} + +bool GDScriptTest::generate_output() { + TestResult result = execute_test_code(true); + if (result.status == GDTEST_LOAD_ERROR) { + return false; + } + + Error err = OK; + FileAccessRef out_file = FileAccess::open(output_file, FileAccess::WRITE, &err); + if (err != OK) { + return false; + } + + String output = result.output.strip_edges(); // TODO: may be hacky. + output += "\n"; // Make sure to insert newline for CI static checks. + + out_file->store_string(output); + out_file->close(); + + return true; +} + +} // namespace GDScriptTests diff --git a/modules/gdscript/tests/gdscript_test_runner.h b/modules/gdscript/tests/gdscript_test_runner.h new file mode 100644 index 0000000000..9b2d14a371 --- /dev/null +++ b/modules/gdscript/tests/gdscript_test_runner.h @@ -0,0 +1,126 @@ +/*************************************************************************/ +/* gdscript_test_runner.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GDSCRIPT_TEST_H +#define GDSCRIPT_TEST_H + +#include "../gdscript.h" +#include "core/error/error_macros.h" +#include "core/string/print_string.h" +#include "core/string/ustring.h" +#include "core/templates/vector.h" + +namespace GDScriptTests { + +void init_autoloads(); +void init_language(const String &p_base_path); +void finish_language(); + +// Single test instance in a suite. +class GDScriptTest { +public: + enum TestStatus { + GDTEST_OK, + GDTEST_LOAD_ERROR, + GDTEST_PARSER_ERROR, + GDTEST_ANALYZER_ERROR, + GDTEST_COMPILER_ERROR, + GDTEST_RUNTIME_ERROR, + }; + + struct TestResult { + TestStatus status; + String output; + bool passed; + }; + +private: + struct ErrorHandlerData { + TestResult *result; + GDScriptTest *self; + ErrorHandlerData(TestResult *p_result, GDScriptTest *p_this) { + result = p_result; + self = p_this; + } + }; + + String source_file; + String output_file; + String base_dir; + + PrintHandlerList _print_handler; + ErrorHandlerList _error_handler; + + void enable_stdout(); + void disable_stdout(); + bool check_output(const String &p_output) const; + String get_text_for_status(TestStatus p_status) const; + + TestResult execute_test_code(bool p_is_generating); + +public: + static void print_handler(void *p_this, const String &p_message, bool p_error); + static void error_handler(void *p_this, const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_explanation, ErrorHandlerType p_type); + TestResult run_test(); + bool generate_output(); + + const String &get_source_file() const { return source_file; } + const String &get_output_file() const { return output_file; } + + GDScriptTest(const String &p_source_path, const String &p_output_path, const String &p_base_dir); + GDScriptTest() : + GDScriptTest(String(), String(), String()) {} // Needed to use in Vector. +}; + +class GDScriptTestRunner { + String source_dir; + Vector<GDScriptTest> tests; + + bool is_generating = false; + bool do_init_languages = false; + + bool make_tests(); + bool make_tests_for_dir(const String &p_dir); + bool generate_class_index(); + +public: + static StringName test_function_name; + + static void handle_cmdline(); + int run_tests(); + bool generate_outputs(); + + GDScriptTestRunner(const String &p_source_dir, bool p_init_language); + ~GDScriptTestRunner(); +}; + +} // namespace GDScriptTests + +#endif // GDSCRIPT_TEST_H diff --git a/modules/gdscript/tests/gdscript_test_runner_suite.h b/modules/gdscript/tests/gdscript_test_runner_suite.h new file mode 100644 index 0000000000..cf4e61f07d --- /dev/null +++ b/modules/gdscript/tests/gdscript_test_runner_suite.h @@ -0,0 +1,74 @@ +/*************************************************************************/ +/* gdscript_test_runner_suite.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef GDSCRIPT_TEST_RUNNER_SUITE_H +#define GDSCRIPT_TEST_RUNNER_SUITE_H + +#include "gdscript_test_runner.h" +#include "tests/test_macros.h" + +namespace GDScriptTests { + +TEST_SUITE("[Modules][GDScript]") { + // GDScript 2.0 is still under heavy construction. + // Allow the tests to fail, but do not ignore errors during development. + // Update the scripts and expected output as needed. + TEST_CASE("Script compilation and runtime") { + GDScriptTestRunner runner("modules/gdscript/tests/scripts", true); + int fail_count = runner.run_tests(); + INFO("Make sure `*.out` files have expected results."); + REQUIRE_MESSAGE(fail_count == 0, "All GDScript tests should pass."); + } +} + +TEST_CASE("[Modules][GDScript] Load source code dynamically and run it") { + Ref<GDScript> gdscript = memnew(GDScript); + gdscript->set_source_code(R"( +extends RefCounted + +func _init(): + set_meta("result", 42) +)"); + // A spurious `Condition "err" is true` message is printed (despite parsing being successful and returning `OK`). + // Silence it. + ERR_PRINT_OFF; + const Error error = gdscript->reload(); + ERR_PRINT_ON; + CHECK_MESSAGE(error == OK, "The script should parse successfully."); + + // Run the script by assigning it to a reference-counted object. + Ref<RefCounted> ref_counted = memnew(RefCounted); + ref_counted->set_script(gdscript); + CHECK_MESSAGE(int(ref_counted->get_meta("result")) == 42, "The script should assign object metadata successfully."); +} + +} // namespace GDScriptTests + +#endif // GDSCRIPT_TEST_RUNNER_SUITE_H diff --git a/modules/gdscript/tests/scripts/.gitignore b/modules/gdscript/tests/scripts/.gitignore new file mode 100644 index 0000000000..94c5b1bf6b --- /dev/null +++ b/modules/gdscript/tests/scripts/.gitignore @@ -0,0 +1,2 @@ +# Ignore metadata if someone open this on Godot. +/.godot diff --git a/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.gd b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.gd new file mode 100644 index 0000000000..d21d8bce96 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.gd @@ -0,0 +1,9 @@ +extends Node + +func test(): + set_name("TestNodeName") + if get_name() == &"TestNodeName": + print("Name is equal") + else: + print("Name is not equal") + print(get_name() is StringName) diff --git a/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out new file mode 100644 index 0000000000..dc4348d9c3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out @@ -0,0 +1,3 @@ +GDTEST_OK +Name is equal +True diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_argument.gd b/modules/gdscript/tests/scripts/parser/errors/missing_argument.gd new file mode 100644 index 0000000000..c56ad94095 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_argument.gd @@ -0,0 +1,6 @@ +func args(a, b): + print(a) + print(b) + +func test(): + args(1,) diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_argument.out b/modules/gdscript/tests/scripts/parser/errors/missing_argument.out new file mode 100644 index 0000000000..fc2a891109 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_argument.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Too few arguments for "args()" call. Expected at least 2 but received 1. diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd b/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd new file mode 100644 index 0000000000..a1077e1985 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd @@ -0,0 +1,2 @@ +func test(): + var a = ("missing paren ->" diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.out b/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.out new file mode 100644 index 0000000000..7326afa33d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected closing ")" after grouping expression. diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd b/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd new file mode 100644 index 0000000000..62cb633e9e --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd @@ -0,0 +1,3 @@ +func test(): + if true # Missing colon here. + print("true") diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_colon.out b/modules/gdscript/tests/scripts/parser/errors/missing_colon.out new file mode 100644 index 0000000000..687b963bc8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_colon.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected ":" after "if" condition. diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd b/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd new file mode 100644 index 0000000000..116b0151da --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd @@ -0,0 +1,6 @@ +func args(a, b): + print(a) + print(b) + +func test(): + args(1,2 diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.out b/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.out new file mode 100644 index 0000000000..34ea7ac323 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected closing ")" after call arguments. diff --git a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.gd b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.gd new file mode 100644 index 0000000000..9ad77f1432 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.gd @@ -0,0 +1,3 @@ +func test(): + print("Using spaces") + print("Using tabs") diff --git a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out new file mode 100644 index 0000000000..6390de9788 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Used "\t" for indentation instead " " as used before in the file. diff --git a/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd b/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd new file mode 100644 index 0000000000..3875ce3936 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd @@ -0,0 +1,3 @@ +extends Node +func test(): + var a = $ # Expected some node path. diff --git a/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.out b/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.out new file mode 100644 index 0000000000..b3dc181a22 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expect node path as string or identifier after "$". diff --git a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd new file mode 100644 index 0000000000..6fd2692d47 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd @@ -0,0 +1,3 @@ +extends Node +func test(): + $23 # Can't use number here. diff --git a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.out b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.out new file mode 100644 index 0000000000..b3dc181a22 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expect node path as string or identifier after "$". diff --git a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd new file mode 100644 index 0000000000..1836d42226 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd @@ -0,0 +1,3 @@ +extends Node +func test(): + $MyNode/23 # Can't use number here. diff --git a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.out b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.out new file mode 100644 index 0000000000..dcb4ccecb0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expect node path after "/". diff --git a/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd new file mode 100644 index 0000000000..08f2eedb2d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd @@ -0,0 +1,2 @@ +func test(): + print("A"); print("B") diff --git a/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out new file mode 100644 index 0000000000..fc03f3efe8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out @@ -0,0 +1,3 @@ +GDTEST_OK +A +B diff --git a/modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.gd b/modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.gd new file mode 100644 index 0000000000..6097b11b10 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.gd @@ -0,0 +1,7 @@ +# See https://github.com/godotengine/godot/issues/41066. + +func f(p, ): ## <-- no errors + print(p) + +func test(): + f(0, ) ## <-- no error diff --git a/modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.out b/modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.out new file mode 100644 index 0000000000..94e2ec2af8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/trailing_comma_in_function_args.out @@ -0,0 +1,2 @@ +GDTEST_OK +0 diff --git a/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd b/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd new file mode 100644 index 0000000000..3b48f10ca7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd @@ -0,0 +1,12 @@ +var a # No init. +var b = 42 # Init. + +func test(): + var c # No init, local. + var d = 23 # Init, local. + + a = 1 + c = 2 + + prints(a, b, c, d) + print("OK") diff --git a/modules/gdscript/tests/scripts/parser/features/variable_declaration.out b/modules/gdscript/tests/scripts/parser/features/variable_declaration.out new file mode 100644 index 0000000000..2e0a63c024 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/variable_declaration.out @@ -0,0 +1,7 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> UNASSIGNED_VARIABLE +>> The variable 'c' was used but never assigned a value. +1 42 2 23 +OK diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd new file mode 100644 index 0000000000..68e3bd424f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd @@ -0,0 +1,2 @@ +func test(): + var unused = "not used" diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_variable.out b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.out new file mode 100644 index 0000000000..270e0e69c0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 2 +>> UNUSED_VARIABLE +>> The local variable 'unused' is declared but never used in the block. If this is intended, prefix it with an underscore: '_unused' diff --git a/modules/gdscript/tests/scripts/project.godot b/modules/gdscript/tests/scripts/project.godot new file mode 100644 index 0000000000..25b49c0abd --- /dev/null +++ b/modules/gdscript/tests/scripts/project.godot @@ -0,0 +1,10 @@ +; This is not an actual project. +; This config only exists to properly set up the test environment. +; It also helps for opening Godot to edit the scripts, but please don't +; let the editor changes be saved. + +config_version=4 + +[application] + +config/name="GDScript Integration Test Suite" diff --git a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd new file mode 100644 index 0000000000..10780b5379 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd @@ -0,0 +1,5 @@ +func test(): + var node := Node.new() + var inside_tree = node.is_inside_tree + node.free() + inside_tree.call() diff --git a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out new file mode 100644 index 0000000000..e585c374e2 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/callable_call_after_free_object.gd +>> 5 +>> Attempt to call function 'null::is_inside_tree (Callable)' on a null instance. diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp index 898ac653f5..7aa5895981 100644 --- a/modules/gdscript/tests/test_gdscript.cpp +++ b/modules/gdscript/tests/test_gdscript.cpp @@ -31,8 +31,8 @@ #include "test_gdscript.h" #include "core/config/project_settings.h" +#include "core/io/file_access.h" #include "core/io/file_access_pack.h" -#include "core/os/file_access.h" #include "core/os/main_loop.h" #include "core/os/os.h" #include "core/string/string_builder.h" @@ -47,7 +47,7 @@ #include "editor/editor_settings.h" #endif -namespace TestGDScript { +namespace GDScriptTests { static void test_tokenizer(const String &p_code, const Vector<String> &p_lines) { GDScriptTokenizer tokenizer; @@ -66,7 +66,7 @@ static void test_tokenizer(const String &p_code, const Vector<String> &p_lines) StringBuilder token; token += " --> "; // Padding for line number. - for (int l = current.start_line; l <= current.end_line; l++) { + for (int l = current.start_line; l <= current.end_line && l <= p_lines.size(); l++) { print_line(vformat("%04d %s", l, p_lines[l - 1]).replace("\t", tab)); } @@ -119,9 +119,21 @@ static void test_parser(const String &p_code, const String &p_script_path, const } } - GDScriptParser::TreePrinter printer; + GDScriptAnalyzer analyzer(&parser); + analyzer.analyze(); + + if (err != OK) { + const List<GDScriptParser::ParserError> &errors = parser.get_errors(); + for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) { + const GDScriptParser::ParserError &error = E->get(); + print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message)); + } + } +#ifdef TOOLS_ENABLED + GDScriptParser::TreePrinter printer; printer.print_tree(parser); +#endif } static void test_compiler(const String &p_code, const String &p_script_path, const Vector<String> &p_lines) { @@ -153,7 +165,7 @@ static void test_compiler(const String &p_code, const String &p_script_path, con GDScriptCompiler compiler; Ref<GDScript> script; - script.instance(); + script.instantiate(); script->set_path(p_script_path); err = compiler.compile(&parser, script.ptr(), false); @@ -175,67 +187,14 @@ static void test_compiler(const String &p_code, const String &p_script_path, con signature += func->get_argument_name(i); } print_line(signature + ")"); - +#ifdef TOOLS_ENABLED func->disassemble(p_lines); +#endif print_line(""); print_line(""); } } -void init_autoloads() { - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - - // First pass, add the constants so they exist before any script is loaded. - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->get(); - - if (info.is_singleton) { - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - ScriptServer::get_language(i)->add_global_constant(info.name, Variant()); - } - } - } - - // Second pass, load into global constants. - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->get(); - - if (!info.is_singleton) { - // Skip non-singletons since we don't have a scene tree here anyway. - continue; - } - - RES res = ResourceLoader::load(info.path); - ERR_CONTINUE_MSG(res.is_null(), "Can't autoload: " + info.path); - Node *n = nullptr; - if (res->is_class("PackedScene")) { - Ref<PackedScene> ps = res; - n = ps->instance(); - } else if (res->is_class("Script")) { - Ref<Script> script_res = res; - StringName ibt = script_res->get_instance_base_type(); - bool valid_type = ClassDB::is_parent_class(ibt, "Node"); - ERR_CONTINUE_MSG(!valid_type, "Script does not inherit a Node: " + info.path); - - Object *obj = ClassDB::instance(ibt); - - ERR_CONTINUE_MSG(obj == nullptr, - "Cannot instance script for autoload, expected 'Node' inheritance, got: " + - String(ibt)); - - n = Object::cast_to<Node>(obj); - n->set_script(script_res); - } - - ERR_CONTINUE_MSG(!n, "Path in autoload not a node or script: " + info.path); - n->set_name(info.name); - - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - ScriptServer::get_language(i)->add_global_constant(info.name, n); - } - } -} - void test(TestType p_type) { List<String> cmdlargs = OS::get_singleton()->get_cmdline_args(); @@ -252,24 +211,12 @@ void test(TestType p_type) { FileAccessRef fa = FileAccess::open(test, FileAccess::READ); ERR_FAIL_COND_MSG(!fa, "Could not open file: " + test); - // Init PackedData since it's used by ProjectSettings. - PackedData *packed_data = memnew(PackedData); - - // Setup project settings since it's needed by the languages to get the global scripts. - // This also sets up the base resource path. - Error err = ProjectSettings::get_singleton()->setup(fa->get_path_absolute().get_base_dir(), String(), true); - if (err) { - print_line("Could not load project settings."); - // Keep going since some scripts still work without this. - } - // Initialize the language for the test routine. - ScriptServer::init_languages(); - init_autoloads(); + init_language(fa->get_path_absolute().get_base_dir()); Vector<uint8_t> buf; - int flen = fa->get_len(); - buf.resize(fa->get_len() + 1); + uint64_t flen = fa->get_length(); + buf.resize(flen + 1); fa->get_buffer(buf.ptrw(), flen); buf.write[flen] = 0; @@ -299,8 +246,6 @@ void test(TestType p_type) { print_line("Not implemented."); } - // Destroy stuff we set up earlier. - ScriptServer::finish_languages(); - memdelete(packed_data); + finish_language(); } -} // namespace TestGDScript +} // namespace GDScriptTests diff --git a/modules/gdscript/tests/test_gdscript.h b/modules/gdscript/tests/test_gdscript.h index bbda46cdad..c7ee5a2208 100644 --- a/modules/gdscript/tests/test_gdscript.h +++ b/modules/gdscript/tests/test_gdscript.h @@ -31,7 +31,10 @@ #ifndef TEST_GDSCRIPT_H #define TEST_GDSCRIPT_H -namespace TestGDScript { +#include "gdscript_test_runner.h" +#include "tests/test_macros.h" + +namespace GDScriptTests { enum TestType { TEST_TOKENIZER, @@ -41,6 +44,7 @@ enum TestType { }; void test(TestType p_type); -} // namespace TestGDScript + +} // namespace GDScriptTests #endif // TEST_GDSCRIPT_H |