diff options
Diffstat (limited to 'modules/gdscript')
27 files changed, 689 insertions, 261 deletions
diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp index 00179109a3..659140b9b1 100644 --- a/modules/gdscript/editor/gdscript_docgen.cpp +++ b/modules/gdscript/editor/gdscript_docgen.cpp @@ -32,21 +32,29 @@ #include "../gdscript.h" -String GDScriptDocGen::_get_script_path(const String &p_path) { +#include "core/config/project_settings.h" + +HashMap<String, String> GDScriptDocGen::singletons; + +String GDScriptDocGen::_get_script_name(const String &p_path) { + const HashMap<String, String>::ConstIterator E = singletons.find(p_path); + if (E) { + return E->value; + } return p_path.trim_prefix("res://").quote(); } String GDScriptDocGen::_get_class_name(const GDP::ClassNode &p_class) { const GDP::ClassNode *curr_class = &p_class; if (!curr_class->identifier) { // All inner classes have an identifier, so this is the outer class. - return _get_script_path(curr_class->fqcn); + return _get_script_name(curr_class->fqcn); } String full_name = curr_class->identifier->name; while (curr_class->outer) { curr_class = curr_class->outer; if (!curr_class->identifier) { // All inner classes have an identifier, so this is the outer class. - return vformat("%s.%s", _get_script_path(curr_class->fqcn), full_name); + return vformat("%s.%s", _get_script_name(curr_class->fqcn), full_name); } full_name = vformat("%s.%s", curr_class->identifier->name, full_name); } @@ -97,12 +105,12 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type return; } if (!p_gdtype.script_type->get_path().is_empty()) { - r_type = _get_script_path(p_gdtype.script_type->get_path()); + r_type = _get_script_name(p_gdtype.script_type->get_path()); return; } } if (!p_gdtype.script_path.is_empty()) { - r_type = _get_script_path(p_gdtype.script_path); + r_type = _get_script_name(p_gdtype.script_path); return; } r_type = "Object"; @@ -221,24 +229,25 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re } } -void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_class) { +void GDScriptDocGen::_generate_docs(GDScript *p_script, const GDP::ClassNode *p_class) { p_script->_clear_doc(); DocData::ClassDoc &doc = p_script->doc; - doc.script_path = _get_script_path(p_script->get_script_path()); + doc.is_script_doc = true; + if (p_script->local_name == StringName()) { - doc.name = doc.script_path; + // This is an outer unnamed class. + doc.name = _get_script_name(p_script->get_script_path()); } else { + // This is an inner or global outer class. doc.name = p_script->local_name; + if (p_script->_owner) { + doc.name = p_script->_owner->doc.name + "." + doc.name; + } } - if (p_script->_owner) { - doc.name = p_script->_owner->doc.name + "." + doc.name; - doc.script_path = doc.script_path + "." + doc.name; - } - - doc.is_script_doc = true; + doc.script_path = p_script->get_script_path(); if (p_script->base.is_valid() && p_script->base->is_valid()) { if (!p_script->base->doc.name.is_empty()) { @@ -271,7 +280,7 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c // Recursively generate inner class docs. // Needs inner GDScripts to exist: previously generated in GDScriptCompiler::make_scripts(). - GDScriptDocGen::generate_docs(*p_script->subclasses[class_name], inner_class); + GDScriptDocGen::_generate_docs(*p_script->subclasses[class_name], inner_class); } break; case GDP::ClassNode::Member::CONSTANT: { @@ -451,3 +460,13 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c // Add doc to the outer-most class. p_script->_add_doc(doc); } + +void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_class) { + for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) { + if (E.value.is_singleton) { + singletons[E.value.path] = E.key; + } + } + _generate_docs(p_script, p_class); + singletons.clear(); +} diff --git a/modules/gdscript/editor/gdscript_docgen.h b/modules/gdscript/editor/gdscript_docgen.h index a326c02c5f..651a4fb198 100644 --- a/modules/gdscript/editor/gdscript_docgen.h +++ b/modules/gdscript/editor/gdscript_docgen.h @@ -39,10 +39,13 @@ class GDScriptDocGen { using GDP = GDScriptParser; using GDType = GDP::DataType; - static String _get_script_path(const String &p_path); + static HashMap<String, String> singletons; // Script path to singleton name. + + static String _get_script_name(const String &p_path); static String _get_class_name(const GDP::ClassNode &p_class); static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return = false); static String _docvalue_from_variant(const Variant &p_variant, int p_recursion_level = 1); + static void _generate_docs(GDScript *p_script, const GDP::ClassNode *p_class); public: static void generate_docs(GDScript *p_script, const GDP::ClassNode *p_class); diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 3df07f9794..426565bb68 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -35,6 +35,7 @@ #include "core/config/project_settings.h" #include "editor/editor_settings.h" +#include "editor/themes/editor_theme_manager.h" Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) { Dictionary color_map; @@ -62,13 +63,15 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l bool in_member_variable = false; bool in_lambda = false; - bool in_function_name = false; - bool in_variable_declaration = false; + bool in_function_name = false; // Any call. + bool in_function_declaration = false; // Only declaration. + bool in_var_const_declaration = false; bool in_signal_declaration = false; bool expect_type = false; - int in_function_args = 0; - int in_function_arg_dicts = 0; + int in_declaration_params = 0; // The number of opened `(` after func/signal name. + int in_declaration_param_dicts = 0; // The number of opened `{` inside func params. + int in_type_params = 0; // The number of opened `[` after type name. Color keyword_color; Color color; @@ -149,7 +152,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l // Check if it's the whole line. if (end_key_length == 0 || color_regions[c].line_only || from + end_key_length > line_length) { // Don't skip comments, for highlighting markers. - if (color_regions[in_region].start_key.begins_with("#")) { + if (color_regions[in_region].type == ColorRegion::TYPE_COMMENT) { break; } if (from + end_key_length > line_length) { @@ -171,7 +174,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } // Don't skip comments, for highlighting markers. - if (j == line_length && !color_regions[in_region].start_key.begins_with("#")) { + if (j == line_length && color_regions[in_region].type != ColorRegion::TYPE_COMMENT) { continue; } } @@ -179,13 +182,13 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l // If we are in one, find the end key. if (in_region != -1) { Color region_color = color_regions[in_region].color; - if (in_node_path && (color_regions[in_region].start_key == "\"" || color_regions[in_region].start_key == "\'")) { + if (in_node_path && color_regions[in_region].type == ColorRegion::TYPE_STRING) { region_color = node_path_color; } - if (in_node_ref && (color_regions[in_region].start_key == "\"" || color_regions[in_region].start_key == "\'")) { + if (in_node_ref && color_regions[in_region].type == ColorRegion::TYPE_STRING) { region_color = node_ref_color; } - if (in_string_name && (color_regions[in_region].start_key == "\"" || color_regions[in_region].start_key == "\'")) { + if (in_string_name && color_regions[in_region].type == ColorRegion::TYPE_STRING) { region_color = string_name_color; } @@ -193,7 +196,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l highlighter_info["color"] = region_color; color_map[j] = highlighter_info; - if (color_regions[in_region].start_key.begins_with("#")) { + if (color_regions[in_region].type == ColorRegion::TYPE_COMMENT) { int marker_start_pos = from; int marker_len = 0; while (from <= line_length) { @@ -444,12 +447,15 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l if (str[k] == '(') { in_function_name = true; - } else if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR) || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FOR)) { - in_variable_declaration = true; + if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) { + in_function_declaration = true; + } + } else if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR) || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FOR) || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::CONST)) { + in_var_const_declaration = true; } // Check for lambda. - if (in_function_name && prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) { + if (in_function_declaration) { k = j - 1; while (k > 0 && is_whitespace(str[k])) { k--; @@ -474,48 +480,60 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } if (is_a_symbol) { - if (in_function_args > 0) { + if (in_declaration_params > 0) { switch (str[j]) { case '(': - in_function_args += 1; + in_declaration_params += 1; break; case ')': - in_function_args -= 1; + in_declaration_params -= 1; break; case '{': - in_function_arg_dicts += 1; + in_declaration_param_dicts += 1; break; case '}': - in_function_arg_dicts -= 1; + in_declaration_param_dicts -= 1; break; } - } else if (in_function_name && str[j] == '(') { - in_function_args = 1; - in_function_arg_dicts = 0; - } - - if (expect_type && (prev_is_char || str[j] == '=') && str[j] != '[' && str[j] != ',' && str[j] != '.') { - expect_type = false; + } else if ((in_function_declaration || in_signal_declaration || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) && str[j] == '(') { + in_declaration_params = 1; + in_declaration_param_dicts = 0; } - if (j > 0 && str[j - 1] == '-' && str[j] == '>') { - expect_type = true; - } - - if (in_variable_declaration || in_function_args > 0) { - int k = j; - // Skip space. - while (k < line_length && is_whitespace(str[k])) { - k++; + if (expect_type) { + switch (str[j]) { + case '[': + in_type_params += 1; + break; + case ']': + in_type_params -= 1; + break; + case ',': + if (in_type_params <= 0) { + expect_type = false; + } + break; + case ' ': + case '\t': + case '.': + break; + default: + expect_type = false; + break; } - - if (str[k] == ':' && in_function_arg_dicts == 0) { - // Has type hint. + } else { + if (j > 0 && str[j - 1] == '-' && str[j] == '>') { expect_type = true; + in_type_params = 0; + } + if ((in_var_const_declaration || (in_declaration_params == 1 && in_declaration_param_dicts == 0)) && str[j] == ':') { + expect_type = true; + in_type_params = 0; } } - in_variable_declaration = false; + in_function_declaration = false; + in_var_const_declaration = false; in_signal_declaration = false; in_function_name = false; in_lambda = false; @@ -581,7 +599,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l color = member_color; } else if (in_function_name) { next_type = FUNCTION; - if (!in_lambda && prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) { + if (!in_lambda && in_function_declaration) { color = function_definition_color; } else { color = function_color; @@ -737,7 +755,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { for (const String &comment : comments) { String beg = comment.get_slice(" ", 0); String end = comment.get_slice_count(" ") > 1 ? comment.get_slice(" ", 1) : String(); - add_color_region(beg, end, comment_color, end.is_empty()); + add_color_region(ColorRegion::TYPE_COMMENT, beg, end, comment_color, end.is_empty()); } /* Doc comments */ @@ -747,18 +765,20 @@ void GDScriptSyntaxHighlighter::_update_cache() { for (const String &doc_comment : doc_comments) { String beg = doc_comment.get_slice(" ", 0); String end = doc_comment.get_slice_count(" ") > 1 ? doc_comment.get_slice(" ", 1) : String(); - add_color_region(beg, end, doc_comment_color, end.is_empty()); + add_color_region(ColorRegion::TYPE_COMMENT, beg, end, doc_comment_color, end.is_empty()); } + /* Code regions */ + const Color code_region_color = Color(EDITOR_GET("text_editor/theme/highlighting/folded_code_region_color").operator Color(), 1.0); + add_color_region(ColorRegion::TYPE_CODE_REGION, "#region", "", code_region_color, true); + add_color_region(ColorRegion::TYPE_CODE_REGION, "#endregion", "", code_region_color, true); + /* Strings */ string_color = EDITOR_GET("text_editor/theme/highlighting/string_color"); - List<String> strings; - gdscript->get_string_delimiters(&strings); - for (const String &string : strings) { - String beg = string.get_slice(" ", 0); - String end = string.get_slice_count(" ") > 1 ? string.get_slice(" ", 1) : String(); - add_color_region(beg, end, string_color, end.is_empty()); - } + add_color_region(ColorRegion::TYPE_STRING, "\"", "\"", string_color); + add_color_region(ColorRegion::TYPE_STRING, "'", "'", string_color); + add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "\"\"\"", "\"\"\"", string_color); + add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "'''", "'''", string_color); const Ref<Script> scr = _get_edited_resource(); if (scr.is_valid()) { @@ -790,7 +810,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { const String text_edit_color_theme = EDITOR_GET("text_editor/theme/color_theme"); const bool godot_2_theme = text_edit_color_theme == "Godot 2"; - if (godot_2_theme || EditorSettings::get_singleton()->is_dark_theme()) { + if (godot_2_theme || EditorThemeManager::is_dark_theme()) { function_definition_color = Color(0.4, 0.9, 1.0); global_function_color = Color(0.64, 0.64, 0.96); node_path_color = Color(0.72, 0.77, 0.49); @@ -891,20 +911,17 @@ void GDScriptSyntaxHighlighter::_update_cache() { } } -void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only) { - for (int i = 0; i < p_start_key.length(); i++) { - ERR_FAIL_COND_MSG(!is_symbol(p_start_key[i]), "color regions must start with a symbol"); - } +void GDScriptSyntaxHighlighter::add_color_region(ColorRegion::Type p_type, const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only) { + ERR_FAIL_COND_MSG(p_start_key.is_empty(), "Color region start key cannot be empty."); + ERR_FAIL_COND_MSG(!is_symbol(p_start_key[0]), "Color region start key must start with a symbol."); - if (p_end_key.length() > 0) { - for (int i = 0; i < p_end_key.length(); i++) { - ERR_FAIL_COND_MSG(!is_symbol(p_end_key[i]), "color regions must end with a symbol"); - } + if (!p_end_key.is_empty()) { + ERR_FAIL_COND_MSG(!is_symbol(p_end_key[0]), "Color region end key must start with a symbol."); } int at = 0; for (int i = 0; i < color_regions.size(); i++) { - ERR_FAIL_COND_MSG(color_regions[i].start_key == p_start_key, "color region with start key '" + p_start_key + "' already exists."); + ERR_FAIL_COND_MSG(color_regions[i].start_key == p_start_key, "Color region with start key '" + p_start_key + "' already exists."); if (p_start_key.length() < color_regions[i].start_key.length()) { at++; } else { @@ -913,6 +930,7 @@ void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, cons } ColorRegion color_region; + color_region.type = p_type; color_region.color = p_color; color_region.start_key = p_start_key; color_region.end_key = p_end_key; diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h index 090857f397..eb7bb7d801 100644 --- a/modules/gdscript/editor/gdscript_highlighter.h +++ b/modules/gdscript/editor/gdscript_highlighter.h @@ -39,6 +39,15 @@ class GDScriptSyntaxHighlighter : public EditorSyntaxHighlighter { private: struct ColorRegion { + enum Type { + TYPE_NONE, + TYPE_STRING, // `"` and `'`, optional prefix `&`, `^`, or `r`. + TYPE_MULTILINE_STRING, // `"""` and `'''`, optional prefix `r`. + TYPE_COMMENT, // `#` and `##`. + TYPE_CODE_REGION, // `#region` and `#endregion`. + }; + + Type type = TYPE_NONE; Color color; String start_key; String end_key; @@ -94,7 +103,7 @@ private: Color comment_marker_colors[COMMENT_MARKER_MAX]; HashMap<String, CommentMarkerLevel> comment_markers; - void add_color_region(const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only = false); + void add_color_region(ColorRegion::Type p_type, const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only = false); public: virtual void _update_cache() override; diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp index f55b00ebe1..316281209a 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp @@ -47,10 +47,7 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve Error err; Ref<Resource> loaded_res = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err); - if (err) { - ERR_PRINT("Failed to load " + p_path); - return err; - } + ERR_FAIL_COND_V_MSG(err, err, "Failed to load " + p_path); ids = r_ids; ids_ctx_plural = r_ids_ctx_plural; @@ -59,11 +56,11 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve GDScriptParser parser; err = parser.parse(source_code, p_path, false); - ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to parse GDScript with GDScriptParser."); + ERR_FAIL_COND_V_MSG(err, err, "Failed to parse GDScript with GDScriptParser."); GDScriptAnalyzer analyzer(&parser); err = analyzer.analyze(); - ERR_FAIL_COND_V_MSG(err != OK, err, "Failed to analyze GDScript with GDScriptAnalyzer."); + ERR_FAIL_COND_V_MSG(err, err, "Failed to analyze GDScript with GDScriptAnalyzer."); // Traverse through the parsed tree from GDScriptParser. GDScriptParser::ClassNode *c = parser.get_tree(); @@ -197,11 +194,7 @@ void GDScriptEditorTranslationParserPlugin::_assess_expression(const GDScriptPar _assess_expression(binary_op_node->right_operand); } break; case GDScriptParser::Node::CALL: { - const GDScriptParser::CallNode *call_node = static_cast<const GDScriptParser::CallNode *>(p_expression); - _extract_from_call(call_node); - for (int i = 0; i < call_node->arguments.size(); i++) { - _assess_expression(call_node->arguments[i]); - } + _assess_call(static_cast<const GDScriptParser::CallNode *>(p_expression)); } break; case GDScriptParser::Node::CAST: { _assess_expression(static_cast<const GDScriptParser::CastNode *>(p_expression)->operand); @@ -241,6 +234,9 @@ void GDScriptEditorTranslationParserPlugin::_assess_expression(const GDScriptPar } void GDScriptEditorTranslationParserPlugin::_assess_assignment(const GDScriptParser::AssignmentNode *p_assignment) { + _assess_expression(p_assignment->assignee); + _assess_expression(p_assignment->assigned_value); + // Extract the translatable strings coming from assignments. For example, get_node("Label").text = "____" StringName assignee_name; @@ -258,26 +254,18 @@ void GDScriptEditorTranslationParserPlugin::_assess_assignment(const GDScriptPar if (assignee_name != StringName() && assignment_patterns.has(assignee_name) && _is_constant_string(p_assignment->assigned_value)) { // If the assignment is towards one of the extract patterns (text, tooltip_text etc.), and the value is a constant string, we collect the string. ids->push_back(p_assignment->assigned_value->reduced_value); - } else if (assignee_name == fd_filters && p_assignment->assigned_value->type == GDScriptParser::Node::CALL) { - // FileDialog.filters accepts assignment in the form of PackedStringArray. For example, - // get_node("FileDialog").filters = PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"]). - - const GDScriptParser::CallNode *call_node = static_cast<const GDScriptParser::CallNode *>(p_assignment->assigned_value); - if (!call_node->arguments.is_empty() && call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) { - const GDScriptParser::ArrayNode *array_node = static_cast<const GDScriptParser::ArrayNode *>(call_node->arguments[0]); - - // Extract the name in "extension ; name" of PackedStringArray. - for (int i = 0; i < array_node->elements.size(); i++) { - _extract_fd_constant_strings(array_node->elements[i]); - } - } - } else { - // If the assignee is not in extract patterns or the assigned_value is not a constant string, try to see if the assigned_value contains tr(). - _assess_expression(p_assignment->assigned_value); + } else if (assignee_name == fd_filters) { + // Extract from `get_node("FileDialog").filters = <filter array>`. + _extract_fd_filter_array(p_assignment->assigned_value); } } -void GDScriptEditorTranslationParserPlugin::_extract_from_call(const GDScriptParser::CallNode *p_call) { +void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::CallNode *p_call) { + _assess_expression(p_call->callee); + for (int i = 0; i < p_call->arguments.size(); i++) { + _assess_expression(p_call->arguments[i]); + } + // Extract the translatable strings coming from function calls. For example: // tr("___"), get_node("Label").set_text("____"), get_node("LineEdit").set_placeholder("____"). @@ -322,52 +310,56 @@ void GDScriptEditorTranslationParserPlugin::_extract_from_call(const GDScriptPar ids_ctx_plural->push_back(id_ctx_plural); } } else if (first_arg_patterns.has(function_name)) { - if (_is_constant_string(p_call->arguments[0])) { + if (!p_call->arguments.is_empty() && _is_constant_string(p_call->arguments[0])) { ids->push_back(p_call->arguments[0]->reduced_value); } } else if (second_arg_patterns.has(function_name)) { - if (_is_constant_string(p_call->arguments[1])) { + if (p_call->arguments.size() > 1 && _is_constant_string(p_call->arguments[1])) { ids->push_back(p_call->arguments[1]->reduced_value); } } else if (function_name == fd_add_filter) { // Extract the 'JPE Images' in this example - get_node("FileDialog").add_filter("*.jpg; JPE Images"). - _extract_fd_constant_strings(p_call->arguments[0]); - } else if (function_name == fd_set_filter && p_call->arguments[0]->type == GDScriptParser::Node::CALL) { - // FileDialog.set_filters() accepts assignment in the form of PackedStringArray. For example, - // get_node("FileDialog").set_filters( PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"])). - - const GDScriptParser::CallNode *call_node = static_cast<const GDScriptParser::CallNode *>(p_call->arguments[0]); - if (call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) { - const GDScriptParser::ArrayNode *array_node = static_cast<const GDScriptParser::ArrayNode *>(call_node->arguments[0]); - for (int i = 0; i < array_node->elements.size(); i++) { - _extract_fd_constant_strings(array_node->elements[i]); - } + if (!p_call->arguments.is_empty()) { + _extract_fd_filter_string(p_call->arguments[0]); } - } - - if (p_call->callee && p_call->callee->type == GDScriptParser::Node::SUBSCRIPT) { - const GDScriptParser::SubscriptNode *subscript_node = static_cast<const GDScriptParser::SubscriptNode *>(p_call->callee); - if (subscript_node->base && subscript_node->base->type == GDScriptParser::Node::CALL) { - const GDScriptParser::CallNode *call_node = static_cast<const GDScriptParser::CallNode *>(subscript_node->base); - _extract_from_call(call_node); + } else if (function_name == fd_set_filter) { + // Extract from `get_node("FileDialog").set_filters(<filter array>)`. + if (!p_call->arguments.is_empty()) { + _extract_fd_filter_array(p_call->arguments[0]); } } } -void GDScriptEditorTranslationParserPlugin::_extract_fd_constant_strings(const GDScriptParser::ExpressionNode *p_expression) { +void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression) { // Extract the name in "extension ; name". - if (_is_constant_string(p_expression)) { - String arg_val = p_expression->reduced_value; - PackedStringArray arr = arg_val.split(";", true); - if (arr.size() != 2) { - ERR_PRINT("Argument for setting FileDialog has bad format."); - return; - } + PackedStringArray arr = p_expression->reduced_value.operator String().split(";", true); + ERR_FAIL_COND_MSG(arr.size() != 2, "Argument for setting FileDialog has bad format."); ids->push_back(arr[1].strip_edges()); } } +void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_array(const GDScriptParser::ExpressionNode *p_expression) { + const GDScriptParser::ArrayNode *array_node = nullptr; + + if (p_expression->type == GDScriptParser::Node::ARRAY) { + // Extract from `["*.png ; PNG Images","*.gd ; GDScript Files"]` (implicit cast to `PackedStringArray`). + array_node = static_cast<const GDScriptParser::ArrayNode *>(p_expression); + } else if (p_expression->type == GDScriptParser::Node::CALL) { + // Extract from `PackedStringArray(["*.png ; PNG Images","*.gd ; GDScript Files"])`. + const GDScriptParser::CallNode *call_node = static_cast<const GDScriptParser::CallNode *>(p_expression); + if (call_node->get_callee_type() == GDScriptParser::Node::IDENTIFIER && call_node->function_name == SNAME("PackedStringArray") && !call_node->arguments.is_empty() && call_node->arguments[0]->type == GDScriptParser::Node::ARRAY) { + array_node = static_cast<const GDScriptParser::ArrayNode *>(call_node->arguments[0]); + } + } + + if (array_node) { + for (int i = 0; i < array_node->elements.size(); i++) { + _extract_fd_filter_string(array_node->elements[i]); + } + } +} + GDScriptEditorTranslationParserPlugin::GDScriptEditorTranslationParserPlugin() { assignment_patterns.insert("text"); assignment_patterns.insert("placeholder_text"); diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h index fab79a925f..fe876134c2 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.h +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h @@ -61,8 +61,10 @@ class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlug void _assess_expression(const GDScriptParser::ExpressionNode *p_expression); void _assess_assignment(const GDScriptParser::AssignmentNode *p_assignment); - void _extract_from_call(const GDScriptParser::CallNode *p_call); - void _extract_fd_constant_strings(const GDScriptParser::ExpressionNode *p_expression); + void _assess_call(const GDScriptParser::CallNode *p_call); + + void _extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression); + void _extract_fd_filter_array(const GDScriptParser::ExpressionNode *p_expression); public: virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) override; diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 1f0830aa17..0da7752940 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1665,7 +1665,7 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { { HashMap<StringName, MethodInfo>::ConstIterator E = sptr->_signals.find(p_name); if (E) { - r_ret = Signal(this->owner, E->key); + r_ret = Signal(owner, E->key); return true; } } @@ -1674,9 +1674,9 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(p_name); if (E) { if (sptr->rpc_config.has(p_name)) { - r_ret = Callable(memnew(GDScriptRPCCallable(this->owner, E->key))); + r_ret = Callable(memnew(GDScriptRPCCallable(owner, E->key))); } else { - r_ret = Callable(this->owner, E->key); + r_ret = Callable(owner, E->key); } return true; } @@ -2154,7 +2154,7 @@ void GDScriptLanguage::finish() { void GDScriptLanguage::profiling_start() { #ifdef DEBUG_ENABLED - MutexLock lock(this->mutex); + MutexLock lock(mutex); SelfList<GDScriptFunction> *elem = function_list.first(); while (elem) { @@ -2185,7 +2185,7 @@ void GDScriptLanguage::profiling_set_save_native_calls(bool p_enable) { void GDScriptLanguage::profiling_stop() { #ifdef DEBUG_ENABLED - MutexLock lock(this->mutex); + MutexLock lock(mutex); profiling = false; #endif @@ -2195,7 +2195,7 @@ int GDScriptLanguage::profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int current = 0; #ifdef DEBUG_ENABLED - MutexLock lock(this->mutex); + MutexLock lock(mutex); profiling_collate_native_call_data(true); SelfList<GDScriptFunction> *elem = function_list.first(); @@ -2233,7 +2233,7 @@ int GDScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_ int current = 0; #ifdef DEBUG_ENABLED - MutexLock lock(this->mutex); + MutexLock lock(mutex); profiling_collate_native_call_data(false); SelfList<GDScriptFunction> *elem = function_list.first(); @@ -2320,14 +2320,13 @@ struct GDScriptDepSort { void GDScriptLanguage::reload_all_scripts() { #ifdef DEBUG_ENABLED print_verbose("GDScript: Reloading all scripts"); - List<Ref<GDScript>> scripts; + Array scripts; { - MutexLock lock(this->mutex); + MutexLock lock(mutex); SelfList<GDScript> *elem = script_list.first(); while (elem) { - // Scripts will reload all subclasses, so only reload root scripts. - if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) { + if (elem->self()->get_path().is_resource_file()) { print_verbose("GDScript: Found: " + elem->self()->get_path()); scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident } @@ -2348,24 +2347,16 @@ void GDScriptLanguage::reload_all_scripts() { #endif } - //as scripts are going to be reloaded, must proceed without locking here - - scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order - - for (Ref<GDScript> &scr : scripts) { - print_verbose("GDScript: Reloading: " + scr->get_path()); - scr->load_source_code(scr->get_path()); - scr->reload(true); - } + reload_scripts(scripts, true); #endif } -void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) { +void GDScriptLanguage::reload_scripts(const Array &p_scripts, bool p_soft_reload) { #ifdef DEBUG_ENABLED List<Ref<GDScript>> scripts; { - MutexLock lock(this->mutex); + MutexLock lock(mutex); SelfList<GDScript> *elem = script_list.first(); while (elem) { @@ -2386,7 +2377,7 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order for (Ref<GDScript> &scr : scripts) { - bool reload = scr == p_script || to_reload.has(scr->get_base()); + bool reload = p_scripts.has(scr) || to_reload.has(scr->get_base()); if (!reload) { continue; @@ -2409,7 +2400,7 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so } } -//same thing for placeholders + //same thing for placeholders #ifdef TOOLS_ENABLED while (scr->placeholders.size()) { @@ -2437,6 +2428,8 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so for (KeyValue<Ref<GDScript>, HashMap<ObjectID, List<Pair<StringName, Variant>>>> &E : to_reload) { Ref<GDScript> scr = E.key; + print_verbose("GDScript: Reloading: " + scr->get_path()); + scr->load_source_code(scr->get_path()); scr->reload(p_soft_reload); //restore state if saved @@ -2484,12 +2477,18 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so #endif } +void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) { + Array scripts; + scripts.push_back(p_script); + reload_scripts(scripts, p_soft_reload); +} + void GDScriptLanguage::frame() { calls = 0; #ifdef DEBUG_ENABLED if (profiling) { - MutexLock lock(this->mutex); + MutexLock lock(mutex); SelfList<GDScriptFunction> *elem = function_list.first(); while (elem) { diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 7b0e2136ed..2da9b89eb9 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -575,6 +575,7 @@ public: virtual String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems = -1, int p_max_depth = -1) override; virtual void reload_all_scripts() override; + virtual void reload_scripts(const Array &p_scripts, bool p_soft_reload) override; virtual void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) override; virtual void frame() override; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 3fd5b3f519..8584a44493 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -31,6 +31,7 @@ #include "gdscript_analyzer.h" #include "gdscript.h" +#include "gdscript_utility_callable.h" #include "gdscript_utility_functions.h" #include "core/config/engine.h" @@ -3410,8 +3411,8 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a if (!found && (is_self || (base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::BUILTIN))) { String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string(); #ifdef SUGGEST_GODOT4_RENAMES - String rename_hint = String(); - if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_HINT)).booleanize()) { + String rename_hint; + if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) { const char *renamed_function_name = check_for_renamed_identifier(p_call->function_name, p_call->type); if (renamed_function_name) { rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", String(renamed_function_name) + "()"); @@ -3620,8 +3621,8 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod p_identifier->set_datatype(type_from_variant(result, p_identifier)); } else if (base.is_hard_type()) { #ifdef SUGGEST_GODOT4_RENAMES - String rename_hint = String(); - if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_HINT)).booleanize()) { + String rename_hint; + if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) { const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type); if (renamed_identifier_name) { rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name); @@ -3664,8 +3665,8 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } if (base.is_hard_type()) { #ifdef SUGGEST_GODOT4_RENAMES - String rename_hint = String(); - if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_HINT)).booleanize()) { + String rename_hint; + if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) { const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type); if (renamed_identifier_name) { rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name); @@ -4117,6 +4118,19 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident return; } + if (Variant::has_utility_function(name) || GDScriptUtilityFunctions::function_exists(name)) { + p_identifier->is_constant = true; + p_identifier->reduced_value = Callable(memnew(GDScriptUtilityCallable(name))); + MethodInfo method_info; + if (GDScriptUtilityFunctions::function_exists(name)) { + method_info = GDScriptUtilityFunctions::get_function_info(name); + } else { + method_info = Variant::get_utility_function_info(name); + } + p_identifier->set_datatype(make_callable_type(method_info)); + return; + } + // Allow "Variant" here since it might be used for nested enums. if (can_be_builtin && name == SNAME("Variant")) { GDScriptParser::DataType variant; @@ -4129,23 +4143,18 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident } // Not found. - // Check if it's a builtin function. - if (GDScriptUtilityFunctions::function_exists(name)) { - push_error(vformat(R"(Built-in function "%s" cannot be used as an identifier.)", name), p_identifier); - } else { #ifdef SUGGEST_GODOT4_RENAMES - String rename_hint = String(); - if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_HINT)).booleanize()) { - const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type); - if (renamed_identifier_name) { - rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name); - } + String rename_hint; + if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::RENAMED_IN_GODOT_4_HINT)).booleanize()) { + const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type); + if (renamed_identifier_name) { + rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name); } - push_error(vformat(R"(Identifier "%s" not declared in the current scope.%s)", name, rename_hint), p_identifier); + } + push_error(vformat(R"(Identifier "%s" not declared in the current scope.%s)", name, rename_hint), p_identifier); #else - push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier); + push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier); #endif // SUGGEST_GODOT4_RENAMES - } GDScriptParser::DataType dummy; dummy.kind = GDScriptParser::DataType::VARIANT; p_identifier->set_datatype(dummy); // Just so type is set to something. @@ -4908,8 +4917,19 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo } result.builtin_type = p_property.type; if (p_property.type == Variant::OBJECT) { - result.kind = GDScriptParser::DataType::NATIVE; - result.native_type = p_property.class_name == StringName() ? SNAME("Object") : p_property.class_name; + if (ScriptServer::is_global_class(p_property.class_name)) { + result.kind = GDScriptParser::DataType::SCRIPT; + result.script_path = ScriptServer::get_global_class_path(p_property.class_name); + result.native_type = ScriptServer::get_global_class_native_base(p_property.class_name); + + Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_property.class_name)); + if (scr.is_valid()) { + result.script_type = scr; + } + } else { + result.kind = GDScriptParser::DataType::NATIVE; + 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; @@ -5315,8 +5335,21 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator return result; } -// TODO: Add safe/unsafe return variable (for variant cases) bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion, const GDScriptParser::Node *p_source_node) { +#ifdef DEBUG_ENABLED + if (p_source_node) { + if (p_target.kind == GDScriptParser::DataType::ENUM) { + if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) { + parser->push_warning(p_source_node, GDScriptWarning::INT_AS_ENUM_WITHOUT_CAST); + } + } + } +#endif + return check_type_compatibility(p_target, p_source, p_allow_implicit_conversion, p_source_node); +} + +// TODO: Add safe/unsafe return variable (for variant cases) +bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion, const GDScriptParser::Node *p_source_node) { // These return "true" so it doesn't affect users negatively. ERR_FAIL_COND_V_MSG(!p_target.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset target type"); ERR_FAIL_COND_V_MSG(!p_source.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset value type"); @@ -5351,11 +5384,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ if (p_target.kind == GDScriptParser::DataType::ENUM) { if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) { -#ifdef DEBUG_ENABLED - if (p_source_node) { - parser->push_warning(p_source_node, GDScriptWarning::INT_AS_ENUM_WITHOUT_CAST); - } -#endif return true; } if (p_source.kind == GDScriptParser::DataType::ENUM) { diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 4ed476a3df..e398ccfdbb 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -147,6 +147,7 @@ public: Variant make_variable_default_value(GDScriptParser::VariableNode *p_variable); const HashMap<String, Ref<GDScriptParserRef>> &get_depended_parsers(); + static bool check_type_compatibility(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr); GDScriptAnalyzer(GDScriptParser *p_parser); }; diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 9bface6136..f902cb10cc 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -121,7 +121,7 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { RBMap<MethodBind *, int> method_bind_map; RBMap<GDScriptFunction *, int> lambdas_map; -#if DEBUG_ENABLED +#ifdef DEBUG_ENABLED // Keep method and property names for pointer and validated operations. // Used when disassembling the bytecode. Vector<String> operator_names; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index f6633f8bf6..13ed66710c 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -37,6 +37,7 @@ #include "core/config/engine.h" #include "core/config/project_settings.h" +#include "core/core_string_names.h" bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringName &p_name) { if (codegen.function_node && codegen.function_node->is_static) { @@ -345,7 +346,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code scr = scr->_base; } - if (nc && (ClassDB::has_signal(nc->get_name(), identifier) || ClassDB::has_method(nc->get_name(), identifier))) { + if (nc && (identifier == CoreStringNames::get_singleton()->_free || ClassDB::has_signal(nc->get_name(), identifier) || ClassDB::has_method(nc->get_name(), identifier))) { // Get like it was a property. GDScriptCodeGenerator::Address temp = codegen.add_temporary(); // TODO: Get type here. GDScriptCodeGenerator::Address self(GDScriptCodeGenerator::Address::SELF); @@ -1375,7 +1376,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code return GDScriptCodeGenerator::Address(); } - codegen.script->lambda_info.insert(function, { lambda->captures.size(), lambda->use_self }); + codegen.script->lambda_info.insert(function, { (int)lambda->captures.size(), lambda->use_self }); gen->write_lambda(result, function, captures, lambda->use_self); for (int i = 0; i < captures.size(); i++) { diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 9ad2ba1914..44e104da05 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -552,6 +552,19 @@ static int _get_property_location(const StringName &p_class, const StringName &p return depth | ScriptLanguage::LOCATION_PARENT_MASK; } +static int _get_property_location(Ref<Script> p_script, const StringName &p_property) { + int depth = 0; + Ref<Script> scr = p_script; + while (scr.is_valid()) { + if (scr->get_member_line(p_property) != -1) { + return depth | ScriptLanguage::LOCATION_PARENT_MASK; + } + depth++; + scr = scr->get_base_script(); + } + return depth + _get_property_location(p_script->get_instance_base_type(), p_property); +} + static int _get_constant_location(const StringName &p_class, const StringName &p_constant) { if (!ClassDB::has_integer_constant(p_class, p_constant)) { return ScriptLanguage::LOCATION_OTHER; @@ -567,6 +580,19 @@ static int _get_constant_location(const StringName &p_class, const StringName &p return depth | ScriptLanguage::LOCATION_PARENT_MASK; } +static int _get_constant_location(Ref<Script> p_script, const StringName &p_constant) { + int depth = 0; + Ref<Script> scr = p_script; + while (scr.is_valid()) { + if (scr->get_member_line(p_constant) != -1) { + return depth | ScriptLanguage::LOCATION_PARENT_MASK; + } + depth++; + scr = scr->get_base_script(); + } + return depth + _get_constant_location(p_script->get_instance_base_type(), p_constant); +} + static int _get_signal_location(const StringName &p_class, const StringName &p_signal) { if (!ClassDB::has_signal(p_class, p_signal)) { return ScriptLanguage::LOCATION_OTHER; @@ -582,6 +608,19 @@ static int _get_signal_location(const StringName &p_class, const StringName &p_s return depth | ScriptLanguage::LOCATION_PARENT_MASK; } +static int _get_signal_location(Ref<Script> p_script, const StringName &p_signal) { + int depth = 0; + Ref<Script> scr = p_script; + while (scr.is_valid()) { + if (scr->get_member_line(p_signal) != -1) { + return depth | ScriptLanguage::LOCATION_PARENT_MASK; + } + depth++; + scr = scr->get_base_script(); + } + return depth + _get_signal_location(p_script->get_instance_base_type(), p_signal); +} + static int _get_method_location(const StringName &p_class, const StringName &p_method) { if (!ClassDB::has_method(p_class, p_method)) { return ScriptLanguage::LOCATION_OTHER; @@ -597,6 +636,19 @@ static int _get_method_location(const StringName &p_class, const StringName &p_m return depth | ScriptLanguage::LOCATION_PARENT_MASK; } +static int _get_method_location(Ref<Script> p_script, const StringName &p_method) { + int depth = 0; + Ref<Script> scr = p_script; + while (scr.is_valid()) { + if (scr->get_member_line(p_method) != -1) { + return depth | ScriptLanguage::LOCATION_PARENT_MASK; + } + depth++; + scr = scr->get_base_script(); + } + return depth + _get_method_location(p_script->get_instance_base_type(), p_method); +} + static int _get_enum_constant_location(const StringName &p_class, const StringName &p_enum_constant) { if (!ClassDB::get_integer_constant_enum(p_class, p_enum_constant)) { return ScriptLanguage::LOCATION_OTHER; @@ -1083,13 +1135,13 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base List<PropertyInfo> members; scr->get_script_property_list(&members); for (const PropertyInfo &E : members) { - if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP)) { + if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_INTERNAL)) { continue; } if (E.name.contains("/")) { continue; } - int location = p_recursion_depth + _get_property_location(scr->get_class_name(), E.name); + int location = p_recursion_depth + _get_property_location(scr, E.name); ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); r_result.insert(option.display, option); } @@ -1097,7 +1149,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base List<MethodInfo> signals; scr->get_script_signal_list(&signals); for (const MethodInfo &E : signals) { - int location = p_recursion_depth + _get_signal_location(scr->get_class_name(), E.name); + int location = p_recursion_depth + _get_signal_location(scr, E.name); ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); r_result.insert(option.display, option); } @@ -1105,7 +1157,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base HashMap<StringName, Variant> constants; scr->get_constants(&constants); for (const KeyValue<StringName, Variant> &E : constants) { - int location = p_recursion_depth + _get_constant_location(scr->get_class_name(), E.key); + int location = p_recursion_depth + _get_constant_location(scr, E.key); ScriptLanguage::CodeCompletionOption option(E.key.operator String(), ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); r_result.insert(option.display, option); } @@ -1117,7 +1169,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base if (E.name.begins_with("@")) { continue; } - int location = p_recursion_depth + _get_method_location(scr->get_class_name(), E.name); + int location = p_recursion_depth + _get_method_location(scr, E.name); ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location); if (E.arguments.size()) { option.insert_text += "("; @@ -1158,7 +1210,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base List<PropertyInfo> pinfo; ClassDB::get_property_list(type, &pinfo); for (const PropertyInfo &E : pinfo) { - if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP)) { + if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_INTERNAL)) { continue; } if (E.name.contains("/")) { @@ -1221,7 +1273,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base } for (const PropertyInfo &E : members) { - if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP)) { + if (E.usage & (PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_INTERNAL)) { continue; } if (!String(E.name).contains("/")) { @@ -1370,7 +1422,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context } } -static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value) { +static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value, GDScriptParser::CompletionContext &p_context) { GDScriptCompletionIdentifier ci; ci.value = p_value; ci.type.is_constant = true; @@ -1392,8 +1444,22 @@ static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value) { scr = obj->get_script(); } if (scr.is_valid()) { - ci.type.script_type = scr; + ci.type.script_path = scr->get_path(); + + if (scr->get_path().ends_with(".gd")) { + Error err; + Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(scr->get_path(), GDScriptParserRef::INTERFACE_SOLVED, err); + if (err == OK) { + ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + ci.type.class_type = parser->get_parser()->get_tree(); + ci.type.kind = GDScriptParser::DataType::CLASS; + p_context.dependent_parsers.push_back(parser); + return ci; + } + } + ci.type.kind = GDScriptParser::DataType::SCRIPT; + ci.type.script_type = scr; ci.type.native_type = scr->get_instance_base_type(); } else { ci.type.kind = GDScriptParser::DataType::NATIVE; @@ -1418,8 +1484,19 @@ static GDScriptCompletionIdentifier _type_from_property(const PropertyInfo &p_pr ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; ci.type.builtin_type = p_property.type; if (p_property.type == Variant::OBJECT) { - ci.type.kind = GDScriptParser::DataType::NATIVE; - ci.type.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; + if (ScriptServer::is_global_class(p_property.class_name)) { + ci.type.kind = GDScriptParser::DataType::SCRIPT; + ci.type.script_path = ScriptServer::get_global_class_path(p_property.class_name); + ci.type.native_type = ScriptServer::get_global_class_native_base(p_property.class_name); + + Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_property.class_name)); + if (scr.is_valid()) { + ci.type.script_type = scr; + } + } else { + ci.type.kind = GDScriptParser::DataType::NATIVE; + ci.type.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name; + } } else { ci.type.kind = GDScriptParser::DataType::BUILTIN; } @@ -1481,7 +1558,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, if (p_expression->is_constant) { // Already has a value, so just use that. - r_type = _type_from_variant(p_expression->reduced_value); + r_type = _type_from_variant(p_expression->reduced_value, p_context); switch (p_expression->get_datatype().kind) { case GDScriptParser::DataType::ENUM: case GDScriptParser::DataType::CLASS: @@ -1495,7 +1572,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, switch (p_expression->type) { case GDScriptParser::Node::LITERAL: { const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(p_expression); - r_type = _type_from_variant(literal->value); + r_type = _type_from_variant(literal->value, p_context); found = true; } break; case GDScriptParser::Node::SELF: { @@ -1676,7 +1753,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, if (!which.is_empty()) { // Try singletons first if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(which)) { - r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which]); + r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which], p_context); found = true; } else { for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) { @@ -1727,7 +1804,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, if (ce.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) { if (ret.get_type() != Variant::OBJECT || ret.operator Object *() != nullptr) { - r_type = _type_from_variant(ret); + r_type = _type_from_variant(ret, p_context); found = true; } } @@ -1755,7 +1832,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, if (base.value.get_type() == Variant::DICTIONARY && base.value.operator Dictionary().has(String(subscript->attribute->name))) { Variant value = base.value.operator Dictionary()[String(subscript->attribute->name)]; - r_type = _type_from_variant(value); + r_type = _type_from_variant(value, p_context); found = true; break; } @@ -1807,7 +1884,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, if (base.value.in(index.value)) { Variant value = base.value.get(index.value); - r_type = _type_from_variant(value); + r_type = _type_from_variant(value, p_context); found = true; break; } @@ -1862,7 +1939,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, bool valid = false; Variant res = base_val.get(index.value, &valid); if (valid) { - r_type = _type_from_variant(res); + r_type = _type_from_variant(res, p_context); r_type.value = Variant(); r_type.type.is_constant = false; found = true; @@ -1920,7 +1997,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, found = false; break; } - r_type = _type_from_variant(res); + r_type = _type_from_variant(res, p_context); if (!v1_use_value || !v2_use_value) { r_type.value = Variant(); r_type.type.is_constant = false; @@ -2009,6 +2086,21 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, default: break; } + } else { + if (p_context.current_class) { + GDScriptCompletionIdentifier base_identifier; + + GDScriptCompletionIdentifier base; + base.value = p_context.base; + base.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + base.type.kind = GDScriptParser::DataType::CLASS; + base.type.class_type = p_context.current_class; + base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static; + + if (_guess_identifier_type_from_base(p_context, base, p_identifier->name, base_identifier)) { + id_type = base_identifier.type; + } + } } while (suite) { @@ -2062,8 +2154,15 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, if (last_assigned_expression && last_assign_line < p_context.current_line) { GDScriptParser::CompletionContext c = p_context; c.current_line = last_assign_line; - r_type.assigned_expression = last_assigned_expression; - if (_guess_expression_type(c, last_assigned_expression, r_type)) { + GDScriptCompletionIdentifier assigned_type; + if (_guess_expression_type(c, last_assigned_expression, assigned_type)) { + if (id_type.is_set() && assigned_type.type.is_set() && !GDScriptAnalyzer::check_type_compatibility(id_type, assigned_type.type)) { + // The assigned type is incompatible. The annotated type takes priority. + r_type.assigned_expression = last_assigned_expression; + r_type.type = id_type; + } else { + r_type = assigned_type; + } return true; } } @@ -2121,20 +2220,6 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, return true; } - // Check current class (including inheritance). - if (p_context.current_class) { - GDScriptCompletionIdentifier base; - base.value = p_context.base; - base.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - base.type.kind = GDScriptParser::DataType::CLASS; - base.type.class_type = p_context.current_class; - base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static; - - if (_guess_identifier_type_from_base(p_context, base, p_identifier->name, r_type)) { - return true; - } - } - // Check global scripts. if (ScriptServer::is_global_class(p_identifier->name)) { String script = ScriptServer::get_global_class_path(p_identifier->name); @@ -2155,7 +2240,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } else { Ref<Script> scr = ResourceLoader::load(ScriptServer::get_global_class_path(p_identifier->name)); if (scr.is_valid()) { - r_type = _type_from_variant(scr); + r_type = _type_from_variant(scr, p_context); r_type.type.is_meta_type = true; return true; } @@ -2165,7 +2250,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, // Check global variables (including autoloads). if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(p_identifier->name)) { - r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier->name]); + r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[p_identifier->name], p_context); return true; } @@ -2218,7 +2303,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & const GDScriptParser::ExpressionNode *init = member.variable->initializer; if (init->is_constant) { r_type.value = init->reduced_value; - r_type = _type_from_variant(init->reduced_value); + r_type = _type_from_variant(init->reduced_value, p_context); return true; } else if (init->start_line == p_context.current_line) { return false; @@ -2245,7 +2330,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & r_type.enumeration = member.m_enum->identifier->name; return true; case GDScriptParser::ClassNode::Member::ENUM_VALUE: - r_type = _type_from_variant(member.enum_value.value); + r_type = _type_from_variant(member.enum_value.value, p_context); return true; case GDScriptParser::ClassNode::Member::SIGNAL: r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; @@ -2281,7 +2366,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & HashMap<StringName, Variant> constants; scr->get_constants(&constants); if (constants.has(p_identifier)) { - r_type = _type_from_variant(constants[p_identifier]); + r_type = _type_from_variant(constants[p_identifier], p_context); return true; } @@ -2345,7 +2430,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & bool valid = false; Variant res = tmp.get(p_identifier, &valid); if (valid) { - r_type = _type_from_variant(res); + r_type = _type_from_variant(res, p_context); r_type.value = Variant(); r_type.type.is_constant = false; return true; @@ -3197,6 +3282,11 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c List<String> opts; p_owner->get_argument_options("get_node", 0, &opts); + bool for_unique_name = false; + if (completion_context.node != nullptr && completion_context.node->type == GDScriptParser::Node::GET_NODE && !static_cast<GDScriptParser::GetNodeNode *>(completion_context.node)->use_dollar) { + for_unique_name = true; + } + for (const String &E : opts) { r_forced = true; String opt = E.strip_edges(); @@ -3205,6 +3295,14 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c // or handle NodePaths which are valid identifiers and don't need quotes. opt = opt.unquote(); } + + if (for_unique_name) { + if (!opt.begins_with("%")) { + continue; + } + opt = opt.substr(1); + } + // The path needs quotes if it's not a valid identifier (with an exception // for "/" as path separator, which also doesn't require quotes). if (!opt.replace("/", "_").is_valid_identifier()) { @@ -3216,11 +3314,13 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c options.insert(option.display, option); } - // Get autoloads. - for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) { - String path = "/root/" + E.key; - ScriptLanguage::CodeCompletionOption option(path.quote(quote_style), ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH); - options.insert(option.display, option); + if (!for_unique_name) { + // Get autoloads. + for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : ProjectSettings::get_singleton()->get_autoload_list()) { + String path = "/root/" + E.key; + ScriptLanguage::CodeCompletionOption option(path.quote(quote_style), ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH); + options.insert(option.display, option); + } } } } break; @@ -3414,6 +3514,12 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } if (ClassDB::has_property(class_name, p_symbol, true)) { + PropertyInfo prop_info; + ClassDB::get_property_info(class_name, p_symbol, &prop_info, true); + if (prop_info.usage & PROPERTY_USAGE_INTERNAL) { + return ERR_CANT_RESOLVE; + } + r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY; r_result.class_name = base_type.native_type; r_result.class_member = p_symbol; @@ -3564,7 +3670,8 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co case GDScriptParser::COMPLETION_ASSIGN: case GDScriptParser::COMPLETION_CALL_ARGUMENTS: case GDScriptParser::COMPLETION_IDENTIFIER: - case GDScriptParser::COMPLETION_PROPERTY_METHOD: { + case GDScriptParser::COMPLETION_PROPERTY_METHOD: + case GDScriptParser::COMPLETION_SUBSCRIPT: { GDScriptParser::DataType base_type; if (context.current_class) { if (context.type != GDScriptParser::COMPLETION_SUPER_METHOD) { diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index a4a12f8bc4..2839d7b123 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -3859,12 +3859,12 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { #ifdef DEBUG_ENABLED - if (this->_is_tool) { + if (_is_tool) { push_error(R"("@tool" annotation can only be used once.)", p_annotation); return false; } #endif // DEBUG_ENABLED - this->_is_tool = true; + _is_tool = true; return true; } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 88b5bdc43f..11c5e51b9a 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -223,7 +223,7 @@ public: } bool operator!=(const DataType &p_other) const { - return !(this->operator==(p_other)); + return !(*this == p_other); } void operator=(const DataType &p_other) { diff --git a/modules/gdscript/gdscript_utility_callable.cpp b/modules/gdscript/gdscript_utility_callable.cpp new file mode 100644 index 0000000000..74d2c477c2 --- /dev/null +++ b/modules/gdscript/gdscript_utility_callable.cpp @@ -0,0 +1,108 @@ +/**************************************************************************/ +/* gdscript_utility_callable.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "gdscript_utility_callable.h" + +#include "core/templates/hashfuncs.h" + +bool GDScriptUtilityCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) { + return p_a->hash() == p_b->hash(); +} + +bool GDScriptUtilityCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) { + return p_a->hash() < p_b->hash(); +} + +uint32_t GDScriptUtilityCallable::hash() const { + return h; +} + +String GDScriptUtilityCallable::get_as_text() const { + String scope; + switch (type) { + case TYPE_INVALID: + scope = "<invalid scope>"; + break; + case TYPE_GLOBAL: + scope = "@GlobalScope"; + break; + case TYPE_GDSCRIPT: + scope = "@GDScript"; + break; + } + return vformat("%s::%s (Callable)", scope, function_name); +} + +CallableCustom::CompareEqualFunc GDScriptUtilityCallable::get_compare_equal_func() const { + return compare_equal; +} + +CallableCustom::CompareLessFunc GDScriptUtilityCallable::get_compare_less_func() const { + return compare_less; +} + +bool GDScriptUtilityCallable::is_valid() const { + return type != TYPE_INVALID; +} + +StringName GDScriptUtilityCallable::get_method() const { + return function_name; +} + +ObjectID GDScriptUtilityCallable::get_object() const { + return ObjectID(); +} + +void GDScriptUtilityCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { + switch (type) { + case TYPE_INVALID: + ERR_PRINT(vformat(R"(Trying to call invalid utility function "%s".)", function_name)); + break; + case TYPE_GLOBAL: + Variant::call_utility_function(function_name, &r_return_value, p_arguments, p_argcount, r_call_error); + break; + case TYPE_GDSCRIPT: + gdscript_function(&r_return_value, p_arguments, p_argcount, r_call_error); + break; + } +} + +GDScriptUtilityCallable::GDScriptUtilityCallable(const StringName &p_function_name) { + function_name = p_function_name; + if (GDScriptUtilityFunctions::function_exists(p_function_name)) { + type = TYPE_GDSCRIPT; + gdscript_function = GDScriptUtilityFunctions::get_function(p_function_name); + } else if (Variant::has_utility_function(p_function_name)) { + type = TYPE_GLOBAL; + } else { + ERR_PRINT(vformat(R"(Unknown utility function "%s".)", p_function_name)); + } + h = p_function_name.hash(); +} diff --git a/modules/gdscript/gdscript_utility_callable.h b/modules/gdscript/gdscript_utility_callable.h new file mode 100644 index 0000000000..675bc4ddd9 --- /dev/null +++ b/modules/gdscript/gdscript_utility_callable.h @@ -0,0 +1,65 @@ +/**************************************************************************/ +/* gdscript_utility_callable.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef GDSCRIPT_UTILITY_CALLABLE_H +#define GDSCRIPT_UTILITY_CALLABLE_H + +#include "gdscript_utility_functions.h" + +#include "core/variant/callable.h" + +class GDScriptUtilityCallable : public CallableCustom { + StringName function_name; + enum Type { + TYPE_INVALID, + TYPE_GLOBAL, + TYPE_GDSCRIPT, + }; + Type type = TYPE_INVALID; + GDScriptUtilityFunctions::FunctionPtr gdscript_function = nullptr; + uint32_t h = 0; + + 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; + bool is_valid() const override; + StringName get_method() 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; + + GDScriptUtilityCallable(const StringName &p_function_name); +}; + +#endif // GDSCRIPT_UTILITY_CALLABLE_H diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 7b03ac74d6..1a8c22cc11 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -655,7 +655,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } bool exit_ok = false; bool awaited = false; - int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0 }; + int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? (int)p_instance->members.size() : 0 }; #endif Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr }; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 36806d2f73..0f8648e9a3 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -48,17 +48,17 @@ lsp::Position GodotPosition::to_lsp(const Vector<String> &p_lines) const { lsp::Position res; // Special case: `line = 0` -> root class (range covers everything). - if (this->line <= 0) { + if (line <= 0) { return res; } // Special case: `line = p_lines.size() + 1` -> root class (range covers everything). - if (this->line >= p_lines.size() + 1) { + if (line >= p_lines.size() + 1) { res.line = p_lines.size(); return res; } - res.line = this->line - 1; + res.line = line - 1; // Note: character outside of `pos_line.length()-1` is valid. - res.character = this->column - 1; + res.character = column - 1; String pos_line = p_lines[res.line]; if (pos_line.contains("\t")) { @@ -67,7 +67,7 @@ lsp::Position GodotPosition::to_lsp(const Vector<String> &p_lines) const { int in_col = 1; int res_char = 0; - while (res_char < pos_line.size() && in_col < this->column) { + while (res_char < pos_line.size() && in_col < column) { if (pos_line[res_char] == '\t') { in_col += tab_size; res_char++; @@ -211,7 +211,7 @@ void ExtendGDScriptParser::update_document_links(const String &p_code) { String value = const_val; lsp::DocumentLink link; link.target = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(scr_path); - link.range = GodotRange(GodotPosition(token.start_line, token.start_column), GodotPosition(token.end_line, token.end_column)).to_lsp(this->lines); + link.range = GodotRange(GodotPosition(token.start_line, token.start_column), GodotPosition(token.end_line, token.end_column)).to_lsp(lines); document_links.push_back(link); } } @@ -222,7 +222,7 @@ void ExtendGDScriptParser::update_document_links(const String &p_code) { lsp::Range ExtendGDScriptParser::range_of_node(const GDScriptParser::Node *p_node) const { GodotPosition start(p_node->start_line, p_node->start_column); GodotPosition end(p_node->end_line, p_node->end_column); - return GodotRange(start, end).to_lsp(this->lines); + return GodotRange(start, end).to_lsp(lines); } void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol) { @@ -394,8 +394,8 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p symbol.name = m.enum_value.identifier->name; symbol.kind = lsp::SymbolKind::EnumMember; symbol.deprecated = false; - symbol.range.start = GodotPosition(m.enum_value.line, m.enum_value.leftmost_column).to_lsp(this->lines); - symbol.range.end = GodotPosition(m.enum_value.line, m.enum_value.rightmost_column).to_lsp(this->lines); + symbol.range.start = GodotPosition(m.enum_value.line, m.enum_value.leftmost_column).to_lsp(lines); + symbol.range.end = GodotPosition(m.enum_value.line, m.enum_value.rightmost_column).to_lsp(lines); symbol.selectionRange = range_of_node(m.enum_value.identifier); symbol.documentation = m.enum_value.doc_data.description; symbol.uri = uri; @@ -430,8 +430,8 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p child.name = value.identifier->name; child.kind = lsp::SymbolKind::EnumMember; child.deprecated = false; - child.range.start = GodotPosition(value.line, value.leftmost_column).to_lsp(this->lines); - child.range.end = GodotPosition(value.line, value.rightmost_column).to_lsp(this->lines); + child.range.start = GodotPosition(value.line, value.leftmost_column).to_lsp(lines); + child.range.end = GodotPosition(value.line, value.rightmost_column).to_lsp(lines); child.selectionRange = range_of_node(value.identifier); child.documentation = value.doc_data.description; child.uri = uri; diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index 95b3be2811..9bf458e031 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -114,7 +114,7 @@ void GDScriptTextDocument::didSave(const Variant &p_param) { scr->update_exports(); ScriptEditor::get_singleton()->reload_scripts(true); ScriptEditor::get_singleton()->update_docs_from_script(scr); - ScriptEditor::get_singleton()->trigger_live_script_reload(); + ScriptEditor::get_singleton()->trigger_live_script_reload(scr->get_path()); } } @@ -421,7 +421,7 @@ Array GDScriptTextDocument::definition(const Dictionary &p_params) { lsp::TextDocumentPositionParams params; params.load(p_params); List<const lsp::DocumentSymbol *> symbols; - Array arr = this->find_symbols(params, symbols); + Array arr = find_symbols(params, symbols); return arr; } @@ -429,7 +429,7 @@ Variant GDScriptTextDocument::declaration(const Dictionary &p_params) { lsp::TextDocumentPositionParams params; params.load(p_params); List<const lsp::DocumentSymbol *> symbols; - Array arr = this->find_symbols(params, symbols); + Array arr = find_symbols(params, symbols); if (arr.is_empty() && !symbols.is_empty() && !symbols.front()->get()->native_class.is_empty()) { // Find a native symbol const lsp::DocumentSymbol *symbol = symbols.front()->get(); if (GDScriptLanguageProtocol::get_singleton()->is_goto_native_symbols_enabled()) { diff --git a/modules/gdscript/tests/README.md b/modules/gdscript/tests/README.md index 361d586d32..cea251bab5 100644 --- a/modules/gdscript/tests/README.md +++ b/modules/gdscript/tests/README.md @@ -6,3 +6,44 @@ and output files. See the [Integration tests for GDScript documentation](https://docs.godotengine.org/en/latest/contributing/development/core_and_modules/unit_testing.html#integration-tests-for-gdscript) for information about creating and running GDScript integration tests. + +# GDScript Autocompletion tests + +The `script/completion` folder contains test for the GDScript autocompletion. + +Each test case consists of at least one `.gd` file, which contains the code, and one `.cfg` file, which contains expected results and configuration. Inside of the GDScript file the character `➡` represents the cursor position, at which autocompletion is invoked. + +The config file contains two section: + +`[input]` contains keys that configure the test environment. The following keys are possible: + +- `cs: boolean = false`: If `true`, the test will be skipped when running a non C# build. +- `use_single_quotes: boolean = false`: Configures the corresponding editor setting for the test. +- `scene: String`: Allows to specify a scene which is opened while autocompletion is performed. If this is not set the test runner will search for a `.tscn` file with the same basename as the GDScript file. If that isn't found either, autocompletion will behave as if no scene was opened. + +`[output]` specifies the expected results for the test. The following key are supported: + +- `include: Array`: An unordered list of suggestions that should be in the result. Each entry is one dictionary with the following keys: `display`, `insert_text`, `kind`, `location`, which correspond to the suggestion struct which is used in the code. The runner only tests against specified keys, so in most cases `display` will suffice. +- `exclude: Array`: An array of suggestions which should not be in the result. The entries take the same form as for `include`. +- `call_hint: String`: The expected call hint returned by autocompletion. +- `forced: boolean`: Whether autocompletion is expected to force opening a completion window. + +Tests will only test against entries in `[output]` that were specified. + +## Writing autocompletion tests + +To avoid failing edge cases a certain behaviour needs to be tested multiple times. Some things that tests should account for: + +- All possible types: Test with all possible types that apply to the tested behaviour. (For the last points testing against `SCRIPT` and `CLASS` should suffice. `CLASS` can be obtained through C#, `SCRIPT` through GDScript. Relying on autoloads to be of type `SCRIPT` is not good, since this might change in the future.) + + - `BUILTIN` + - `NATIVE` + - GDScripts (with `class_name` as well as `preload`ed) + - C# (as standin for all other language bindings) (with `class_name` as well as `preload`ed) + - Autoloads + +- Possible contexts: the completion might be placed in different places of the program. e.g: + - initializers of class members + - directly inside a suite + - assignments inside a suite + - as parameter to a call diff --git a/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg b/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg index 4edee46039..27e695d245 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg +++ b/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg @@ -1,4 +1,4 @@ [output] -expected=[ +include=[ {"display": "autoplay"}, ] diff --git a/modules/gdscript/tests/scripts/runtime/features/free_is_callable.gd b/modules/gdscript/tests/scripts/runtime/features/free_is_callable.gd new file mode 100644 index 0000000000..b9746a8207 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/free_is_callable.gd @@ -0,0 +1,10 @@ +func test(): + var node := Node.new() + var callable: Callable = node.free + callable.call() + print(node) + + node = Node.new() + callable = node["free"] + callable.call() + print(node) diff --git a/modules/gdscript/tests/scripts/runtime/features/free_is_callable.out b/modules/gdscript/tests/scripts/runtime/features/free_is_callable.out new file mode 100644 index 0000000000..97bfc46d96 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/free_is_callable.out @@ -0,0 +1,3 @@ +GDTEST_OK +<Freed Object> +<Freed Object> diff --git a/modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.gd new file mode 100644 index 0000000000..11f064bb83 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.gd @@ -0,0 +1,10 @@ +func test(): + print(print) + print(len) + + prints.callv([1, 2, 3]) + print(mini.call(1, 2)) + print(len.bind("abc").call()) + + const ABSF = absf + print(ABSF.call(-1.2)) diff --git a/modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.out new file mode 100644 index 0000000000..91549b9345 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/utility_func_as_callable.out @@ -0,0 +1,7 @@ +GDTEST_OK +@GlobalScope::print (Callable) +@GDScript::len (Callable) +1 2 3 +1 +3 +1.2 diff --git a/modules/gdscript/tests/test_completion.h b/modules/gdscript/tests/test_completion.h index abc34bd4bf..fd6b5321e6 100644 --- a/modules/gdscript/tests/test_completion.h +++ b/modules/gdscript/tests/test_completion.h @@ -128,19 +128,23 @@ static void test_directory(const String &p_dir) { EditorSettings::get_singleton()->set_setting("text_editor/completion/use_single_quotes", conf.get_value("input", "use_single_quotes", false)); List<Dictionary> include; - to_dict_list(conf.get_value("result", "include", Array()), include); + to_dict_list(conf.get_value("output", "include", Array()), include); List<Dictionary> exclude; - to_dict_list(conf.get_value("result", "exclude", Array()), exclude); + to_dict_list(conf.get_value("output", "exclude", Array()), exclude); List<ScriptLanguage::CodeCompletionOption> options; String call_hint; bool forced; Node *owner = nullptr; - if (dir->file_exists(next.get_basename() + ".tscn")) { - String project_path = "res://completion"; - Ref<PackedScene> scene = ResourceLoader::load(project_path.path_join(next.get_basename() + ".tscn"), "PackedScene"); + if (conf.has_section_key("input", "scene")) { + Ref<PackedScene> scene = ResourceLoader::load(conf.get_value("input", "scene"), "PackedScene"); + if (scene.is_valid()) { + owner = scene->instantiate(); + } + } else if (dir->file_exists(next.get_basename() + ".tscn")) { + Ref<PackedScene> scene = ResourceLoader::load(path.path_join(next.get_basename() + ".tscn"), "PackedScene"); if (scene.is_valid()) { owner = scene->instantiate(); } @@ -169,8 +173,8 @@ static void test_directory(const String &p_dir) { CHECK_MESSAGE(contains_excluded.is_empty(), "Autocompletion suggests illegal option '", contains_excluded, "' for '", path.path_join(next), "'."); CHECK(include.is_empty()); - String expected_call_hint = conf.get_value("result", "call_hint", call_hint); - bool expected_forced = conf.get_value("result", "forced", forced); + String expected_call_hint = conf.get_value("output", "call_hint", call_hint); + bool expected_forced = conf.get_value("output", "forced", forced); CHECK(expected_call_hint == call_hint); CHECK(expected_forced == forced); |