diff options
Diffstat (limited to 'modules/gdscript')
173 files changed, 4935 insertions, 1878 deletions
diff --git a/modules/gdscript/.editorconfig b/modules/gdscript/.editorconfig new file mode 100644 index 0000000000..640c205093 --- /dev/null +++ b/modules/gdscript/.editorconfig @@ -0,0 +1,8 @@ +[*.gd] +indent_style = tab +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.out] +insert_final_newline = true diff --git a/modules/gdscript/SCsub b/modules/gdscript/SCsub index 1dc4768186..61accd4fc9 100644 --- a/modules/gdscript/SCsub +++ b/modules/gdscript/SCsub @@ -19,6 +19,8 @@ if env.editor_build: # Using a define in the disabled case, to avoid having an extra define # in regular builds where all modules are enabled. env_gdscript.Append(CPPDEFINES=["GDSCRIPT_NO_LSP"]) + # Also needed in main env to unexpose --lsp-port option. + env.Append(CPPDEFINES=["GDSCRIPT_NO_LSP"]) if env["tests"]: diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 4f1a256ec9..3da6bcf10c 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -57,11 +57,12 @@ [/codeblock] </description> </method> - <method name="convert"> + <method name="convert" is_deprecated="true"> <return type="Variant" /> <param index="0" name="what" type="Variant" /> <param index="1" name="type" type="int" /> <description> + [i]Deprecated.[/i] Use [method @GlobalScope.type_convert] instead. Converts [param what] to [param type] in the best way possible. The [param type] uses the [enum Variant.Type] values. [codeblock] var a = [4, 2.5, 1.2] @@ -284,11 +285,36 @@ <description> Mark the following property as exported (editable in the Inspector dock and saved to disk). To control the type of the exported property, use the type hint notation. [codeblock] + extends Node + + enum Direction {LEFT, RIGHT, UP, DOWN} + + # Built-in types. @export var string = "" @export var int_number = 5 @export var float_number: float = 5 + + # Enums. + @export var type: Variant.Type + @export var format: Image.Format + @export var direction: Direction + + # Resources. @export var image: Image + @export var custom_resource: CustomResource + + # Nodes. + @export var node: Node + @export var custom_node: CustomNode + + # Typed arrays. + @export var int_array: Array[int] + @export var direction_array: Array[Direction] + @export var image_array: Array[Image] + @export var node_array: Array[Node] [/codeblock] + [b]Note:[/b] Custom resources and nodes must be registered as global classes using [code]class_name[/code]. + [b]Note:[/b] Node export is only supported in [Node]-derived classes and has a number of other limitations. </description> </annotation> <annotation name="@export_category"> @@ -556,7 +582,7 @@ <description> Export an [int] or [float] property as a range value. The range must be defined by [param min] and [param max], as well as an optional [param step] and a variety of extra hints. The [param step] defaults to [code]1[/code] for integer properties. For floating-point numbers this value depends on your [code]EditorSettings.interface/inspector/default_float_step[/code] setting. If hints [code]"or_greater"[/code] and [code]"or_less"[/code] are provided, the editor widget will not cap the value at range boundaries. The [code]"exp"[/code] hint will make the edited values on range to change exponentially. The [code]"hide_slider"[/code] hint will hide the slider element of the editor widget. - Hints also allow to indicate the units for the edited value. Using [code]"radians"[/code] you can specify that the actual value is in radians, but should be displayed in degrees in the Inspector dock. [code]"degrees"[/code] allows to add a degree sign as a unit suffix. Finally, a custom suffix can be provided using [code]"suffix:unit"[/code], where "unit" can be any string. + Hints also allow to indicate the units for the edited value. Using [code]"radians_as_degrees"[/code] you can specify that the actual value is in radians, but should be displayed in degrees in the Inspector dock (the range values are also in degrees). [code]"degrees"[/code] allows to add a degree sign as a unit suffix (the value is unchanged). Finally, a custom suffix can be provided using [code]"suffix:unit"[/code], where "unit" can be any string. See also [constant PROPERTY_HINT_RANGE]. [codeblock] @export_range(0, 20) var number @@ -566,7 +592,7 @@ @export_range(0, 100, 1, "or_greater") var power_percent @export_range(0, 100, 1, "or_greater", "or_less") var health_delta - @export_range(-3.14, 3.14, 0.001, "radians") var angle_radians + @export_range(-180, 180, 0.001, "radians_as_degrees") var angle_radians @export_range(0, 360, 1, "degrees") var angle_degrees @export_range(-8, 8, 2, "suffix:px") var target_offset [/codeblock] @@ -600,8 +626,8 @@ @icon("res://path/to/class/icon.svg") [/codeblock] [b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported. - [b]Note:[/b] As annotations describe their subject, the [code]@icon[/code] annotation must be placed before the class definition and inheritance. - [b]Note:[/b] Unlike other annotations, the argument of the [code]@icon[/code] annotation must be a string literal (constant expressions are not supported). + [b]Note:[/b] As annotations describe their subject, the [annotation @icon] annotation must be placed before the class definition and inheritance. + [b]Note:[/b] Unlike other annotations, the argument of the [annotation @icon] annotation must be a string literal (constant expressions are not supported). </description> </annotation> <annotation name="@onready"> @@ -652,7 +678,7 @@ @tool extends Node [/codeblock] - [b]Note:[/b] As annotations describe their subject, the [code]@tool[/code] annotation must be placed before the class definition and inheritance. + [b]Note:[/b] As annotations describe their subject, the [annotation @tool] annotation must be placed before the class definition and inheritance. </description> </annotation> <annotation name="@warning_ignore" qualifiers="vararg"> diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp index 1aecfc6de1..c3979dd290 100644 --- a/modules/gdscript/editor/gdscript_docgen.cpp +++ b/modules/gdscript/editor/gdscript_docgen.cpp @@ -32,14 +32,11 @@ #include "../gdscript.h" -using GDP = GDScriptParser; -using GDType = GDP::DataType; - -static String _get_script_path(const String &p_path) { +String GDScriptDocGen::_get_script_path(const String &p_path) { return p_path.trim_prefix("res://").quote(); } -static String _get_class_name(const GDP::ClassNode &p_class) { +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); @@ -56,7 +53,7 @@ static String _get_class_name(const GDP::ClassNode &p_class) { return full_name; } -static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return = false) { +void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String &r_enum, bool p_is_return) { if (!p_gdtype.is_hard_type()) { r_type = "Variant"; return; @@ -82,12 +79,21 @@ static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String r_type = Variant::get_type_name(p_gdtype.builtin_type); return; case GDType::NATIVE: + if (p_gdtype.is_meta_type) { + //r_type = GDScriptNativeClass::get_class_static(); + r_type = "Object"; // "GDScriptNativeClass" refers to a blank page. + return; + } r_type = p_gdtype.native_type; return; case GDType::SCRIPT: + if (p_gdtype.is_meta_type) { + r_type = p_gdtype.script_type.is_valid() ? p_gdtype.script_type->get_class() : Script::get_class_static(); + return; + } if (p_gdtype.script_type.is_valid()) { if (p_gdtype.script_type->get_global_name() != StringName()) { - r_type = _get_script_path(p_gdtype.script_type->get_global_name()); + r_type = p_gdtype.script_type->get_global_name(); return; } if (!p_gdtype.script_type->get_path().is_empty()) { @@ -102,9 +108,17 @@ static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String r_type = "Object"; return; case GDType::CLASS: + if (p_gdtype.is_meta_type) { + r_type = GDScript::get_class_static(); + return; + } r_type = _get_class_name(*p_gdtype.class_type); return; case GDType::ENUM: + if (p_gdtype.is_meta_type) { + r_type = "Dictionary"; + return; + } r_type = "int"; r_enum = String(p_gdtype.native_type).replace("::", "."); if (r_enum.begins_with("res://")) { @@ -123,16 +137,100 @@ static void _doctype_from_gdtype(const GDType &p_gdtype, String &r_type, String } } +String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_recursion_level) { + constexpr int MAX_RECURSION_LEVEL = 2; + + switch (p_variant.get_type()) { + case Variant::STRING: + return String(p_variant).c_escape().quote(); + case Variant::OBJECT: + return "<Object>"; + case Variant::DICTIONARY: { + const Dictionary dict = p_variant; + + if (dict.is_empty()) { + return "{}"; + } + + if (p_recursion_level > MAX_RECURSION_LEVEL) { + return "{...}"; + } + + List<Variant> keys; + dict.get_key_list(&keys); + keys.sort(); + + String data; + for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { + if (E->prev()) { + data += ", "; + } + data += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1); + } + + return "{" + data + "}"; + } break; + case Variant::ARRAY: { + const Array array = p_variant; + String result; + + if (array.get_typed_builtin() != Variant::NIL) { + result += "Array["; + + Ref<Script> script = array.get_typed_script(); + if (script.is_valid()) { + if (script->get_global_name() != StringName()) { + result += script->get_global_name(); + } else if (!script->get_path().get_file().is_empty()) { + result += script->get_path().get_file(); + } else { + result += array.get_typed_class_name(); + } + } else if (array.get_typed_class_name() != StringName()) { + result += array.get_typed_class_name(); + } else { + result += Variant::get_type_name((Variant::Type)array.get_typed_builtin()); + } + + result += "]("; + } + + if (array.is_empty()) { + result += "[]"; + } else if (p_recursion_level > MAX_RECURSION_LEVEL) { + result += "[...]"; + } else { + result += "["; + for (int i = 0; i < array.size(); i++) { + if (i > 0) { + result += ", "; + } + result += _docvalue_from_variant(array[i], p_recursion_level + 1); + } + result += "]"; + } + + if (array.get_typed_builtin() != Variant::NIL) { + result += ")"; + } + + return result; + } break; + default: + return p_variant.get_construct_string(); + } +} + 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()); - if (p_script->name.is_empty()) { + if (p_script->local_name == StringName()) { doc.name = doc.script_path; } else { - doc.name = p_script->name; + doc.name = p_script->local_name; } if (p_script->_owner) { @@ -183,7 +281,10 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c p_script->member_lines[const_name] = m_const->start_line; DocData::ConstantDoc const_doc; - DocData::constant_doc_from_variant(const_doc, const_name, m_const->initializer->reduced_value, m_const->doc_data.description); + const_doc.name = const_name; + const_doc.value = _docvalue_from_variant(m_const->initializer->reduced_value); + const_doc.is_value_valid = true; + const_doc.description = m_const->doc_data.description; const_doc.is_deprecated = m_const->doc_data.is_deprecated; const_doc.is_experimental = m_const->doc_data.is_experimental; doc.constants.push_back(const_doc); @@ -203,7 +304,11 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c method_doc.qualifiers = m_func->is_static ? "static" : ""; if (m_func->return_type) { - _doctype_from_gdtype(m_func->return_type->get_datatype(), method_doc.return_type, method_doc.return_enum, true); + // `m_func->return_type->get_datatype()` is a metatype. + _doctype_from_gdtype(m_func->get_datatype(), method_doc.return_type, method_doc.return_enum, true); + } else if (!m_func->body->has_return) { + // If no `return` statement, then return type is `void`, not `Variant`. + method_doc.return_type = "void"; } else { method_doc.return_type = "Variant"; } @@ -214,7 +319,7 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c _doctype_from_gdtype(p->get_datatype(), arg_doc.type, arg_doc.enumeration); if (p->initializer != nullptr) { if (p->initializer->is_constant) { - arg_doc.default_value = p->initializer->reduced_value.get_construct_string().replace("\n", "\\n"); + arg_doc.default_value = _docvalue_from_variant(p->initializer->reduced_value); } else { arg_doc.default_value = "<unknown>"; } @@ -283,7 +388,7 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c if (m_var->initializer) { if (m_var->initializer->is_constant) { - prop_doc.default_value = m_var->initializer->reduced_value.get_construct_string().replace("\n", "\\n"); + prop_doc.default_value = _docvalue_from_variant(m_var->initializer->reduced_value); } else { prop_doc.default_value = "<unknown>"; } @@ -309,7 +414,7 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c for (const GDP::EnumNode::Value &val : m_enum->values) { DocData::ConstantDoc const_doc; const_doc.name = val.identifier->name; - const_doc.value = String(Variant(val.value)); + const_doc.value = _docvalue_from_variant(val.value); const_doc.is_value_valid = true; const_doc.enumeration = name; const_doc.description = val.doc_data.description; @@ -328,8 +433,11 @@ void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_c p_script->member_lines[name] = m_enum_val.identifier->start_line; DocData::ConstantDoc const_doc; - DocData::constant_doc_from_variant(const_doc, name, m_enum_val.value, m_enum_val.doc_data.description); + const_doc.name = name; + const_doc.value = _docvalue_from_variant(m_enum_val.value); + const_doc.is_value_valid = true; const_doc.enumeration = "@unnamed_enums"; + const_doc.description = m_enum_val.doc_data.description; const_doc.is_deprecated = m_enum_val.doc_data.is_deprecated; const_doc.is_experimental = m_enum_val.doc_data.is_experimental; doc.constants.push_back(const_doc); diff --git a/modules/gdscript/editor/gdscript_docgen.h b/modules/gdscript/editor/gdscript_docgen.h index 3357fb680c..a326c02c5f 100644 --- a/modules/gdscript/editor/gdscript_docgen.h +++ b/modules/gdscript/editor/gdscript_docgen.h @@ -36,8 +36,16 @@ #include "core/doc_data.h" class GDScriptDocGen { + using GDP = GDScriptParser; + using GDType = GDP::DataType; + + static String _get_script_path(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); + public: - static void generate_docs(GDScript *p_script, const GDScriptParser::ClassNode *p_class); + static void generate_docs(GDScript *p_script, const GDP::ClassNode *p_class); }; #endif // GDSCRIPT_DOCGEN_H diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index e488d6e266..8dbd262b22 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -52,6 +52,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l bool in_keyword = false; bool in_word = false; bool in_number = false; + bool in_raw_string = false; bool in_node_path = false; bool in_node_ref = false; bool in_annotation = false; @@ -62,11 +63,13 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l bool in_lambda = false; bool in_function_name = false; - bool in_function_args = false; bool in_variable_declaration = false; bool in_signal_declaration = false; bool expect_type = false; + int in_function_args = 0; + int in_function_arg_dicts = 0; + Color keyword_color; Color color; @@ -146,7 +149,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 == "#") { + if (color_regions[in_region].start_key.begins_with("#")) { break; } if (from + end_key_length > line_length) { @@ -168,7 +171,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 != "#") { + if (j == line_length && !color_regions[in_region].start_key.begins_with("#")) { continue; } } @@ -190,7 +193,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 == "#") { + if (color_regions[in_region].start_key.begins_with("#")) { int marker_start_pos = from; int marker_len = 0; while (from <= line_length) { @@ -234,15 +237,33 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } if (str[from] == '\\') { - Dictionary escape_char_highlighter_info; - escape_char_highlighter_info["color"] = symbol_color; - color_map[from] = escape_char_highlighter_info; + if (!in_raw_string) { + Dictionary escape_char_highlighter_info; + escape_char_highlighter_info["color"] = symbol_color; + color_map[from] = escape_char_highlighter_info; + } from++; - Dictionary region_continue_highlighter_info; - region_continue_highlighter_info["color"] = region_color; - color_map[from + 1] = region_continue_highlighter_info; + if (!in_raw_string) { + int esc_len = 0; + if (str[from] == 'u') { + esc_len = 4; + } else if (str[from] == 'U') { + esc_len = 6; + } + for (int k = 0; k < esc_len && from < line_length - 1; k++) { + if (!is_hex_digit(str[from + 1])) { + break; + } + from++; + } + + Dictionary region_continue_highlighter_info; + region_continue_highlighter_info["color"] = region_color; + color_map[from + 1] = region_continue_highlighter_info; + } + continue; } @@ -423,7 +444,7 @@ 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)) { + } else if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR) || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FOR)) { in_variable_declaration = true; } @@ -453,15 +474,27 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } if (is_a_symbol) { - if (in_function_name) { - in_function_args = true; - } - - if (in_function_args && str[j] == ')') { - in_function_args = false; + if (in_function_args > 0) { + switch (str[j]) { + case '(': + in_function_args += 1; + break; + case ')': + in_function_args -= 1; + break; + case '{': + in_function_arg_dicts += 1; + break; + case '}': + in_function_arg_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] != '[') { + if (expect_type && (prev_is_char || str[j] == '=') && str[j] != '[' && str[j] != '.') { expect_type = false; } @@ -469,15 +502,15 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l expect_type = true; } - if (in_variable_declaration || in_function_args) { + if (in_variable_declaration || in_function_args > 0) { int k = j; - // Skip space + // Skip space. while (k < line_length && is_whitespace(str[k])) { k++; } - if (str[k] == ':') { - // has type hint + if (str[k] == ':' && in_function_arg_dicts == 0) { + // Has type hint. expect_type = true; } } @@ -489,6 +522,12 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l in_member_variable = false; } + if (!in_raw_string && in_region == -1 && str[j] == 'r' && j < line_length - 1 && (str[j + 1] == '"' || str[j + 1] == '\'')) { + in_raw_string = true; + } else if (in_raw_string && in_region == -1) { + in_raw_string = false; + } + // Keep symbol color for binary '&&'. In the case of '&&&' use StringName color for the last ampersand. if (!in_string_name && in_region == -1 && str[j] == '&' && !is_binary_op) { if (j >= 2 && str[j - 1] == '&' && str[j - 2] != '&' && prev_is_binary_op) { @@ -520,7 +559,9 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l in_annotation = false; } - if (in_node_ref) { + if (in_raw_string) { + color = string_color; + } else if (in_node_ref) { next_type = NODE_REF; color = node_ref_color; } else if (in_annotation) { @@ -535,16 +576,11 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } else if (in_keyword) { next_type = KEYWORD; color = keyword_color; - } else if (in_member_variable) { - next_type = MEMBER; - color = member_color; } else if (in_signal_declaration) { next_type = SIGNAL; - color = member_color; } else if (in_function_name) { next_type = FUNCTION; - if (!in_lambda && prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) { color = function_definition_color; } else { @@ -559,6 +595,9 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } else if (expect_type) { next_type = TYPE; color = type_color; + } else if (in_member_variable) { + next_type = MEMBER; + color = member_color; } else { next_type = IDENTIFIER; } @@ -656,6 +695,12 @@ void GDScriptSyntaxHighlighter::_update_cache() { for (const String &E : core_types) { class_names[StringName(E)] = basetype_color; } + class_names[SNAME("Variant")] = basetype_color; + class_names[SNAME("void")] = basetype_color; + // `get_core_type_words()` doesn't return primitive types. + class_names[SNAME("bool")] = basetype_color; + class_names[SNAME("int")] = basetype_color; + class_names[SNAME("float")] = basetype_color; /* Reserved words. */ const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color"); @@ -670,6 +715,10 @@ void GDScriptSyntaxHighlighter::_update_cache() { } } + // Highlight `set` and `get` as "keywords" with the function color to avoid conflicts with method calls. + reserved_keywords[SNAME("set")] = function_color; + reserved_keywords[SNAME("get")] = function_color; + /* Global functions. */ List<StringName> global_function_list; GDScriptUtilityFunctions::get_function_list(&global_function_list); @@ -691,8 +740,18 @@ void GDScriptSyntaxHighlighter::_update_cache() { add_color_region(beg, end, comment_color, end.is_empty()); } + /* Doc comments */ + const Color doc_comment_color = EDITOR_GET("text_editor/theme/highlighting/doc_comment_color"); + List<String> doc_comments; + gdscript->get_doc_comment_delimiters(&doc_comments); + 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()); + } + /* Strings */ - const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color"); + string_color = EDITOR_GET("text_editor/theme/highlighting/string_color"); List<String> strings; gdscript->get_string_delimiters(&strings); for (const String &string : strings) { @@ -848,6 +907,8 @@ void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, cons 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 { + break; } } diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h index fe3b63d713..090857f397 100644 --- a/modules/gdscript/editor/gdscript_highlighter.h +++ b/modules/gdscript/editor/gdscript_highlighter.h @@ -78,6 +78,7 @@ private: Color built_in_type_color; Color number_color; Color member_color; + Color string_color; Color node_path_color; Color node_ref_color; Color annotation_color; diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp index 064143f400..9128f104b8 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp @@ -40,7 +40,7 @@ void GDScriptEditorTranslationParserPlugin::get_recognized_extensions(List<Strin } Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural) { - // Extract all translatable strings using the parsed tree from GDSriptParser. + // Extract all translatable strings using the parsed tree from GDScriptParser. // The strategy is to find all ExpressionNode and AssignmentNode from the tree and extract strings if relevant, i.e // Search strings in ExpressionNode -> CallNode -> tr(), set_text(), set_placeholder() etc. // Search strings in AssignmentNode -> text = "__", tooltip_text = "__" etc. @@ -225,10 +225,15 @@ void GDScriptEditorTranslationParserPlugin::_assess_assignment(const GDScriptPar if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) { assignee_name = static_cast<const GDScriptParser::IdentifierNode *>(p_assignment->assignee)->name; } else if (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT) { - assignee_name = static_cast<const GDScriptParser::SubscriptNode *>(p_assignment->assignee)->attribute->name; + const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_assignment->assignee); + if (subscript->is_attribute && subscript->attribute) { + assignee_name = subscript->attribute->name; + } else if (subscript->index && _is_constant_string(subscript->index)) { + assignee_name = subscript->index->reduced_value; + } } - if (assignment_patterns.has(assignee_name) && _is_constant_string(p_assignment->assigned_value)) { + 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) { @@ -236,7 +241,7 @@ void GDScriptEditorTranslationParserPlugin::_assess_assignment(const GDScriptPar // 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[0]->type == GDScriptParser::Node::ARRAY) { + 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. diff --git a/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd b/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd index b8fc8c75dc..28ab080dd2 100644 --- a/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd +++ b/modules/gdscript/editor/script_templates/CharacterBody2D/basic_movement.gd @@ -15,7 +15,7 @@ func _physics_process(delta: float) -> void: if not is_on_floor(): velocity.y += gravity * delta - # Handle Jump. + # Handle jump. if Input.is_action_just_pressed("ui_accept") and is_on_floor(): velocity.y = JUMP_VELOCITY diff --git a/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd b/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd index 53bc606c9a..9b0e4be4ed 100644 --- a/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd +++ b/modules/gdscript/editor/script_templates/CharacterBody3D/basic_movement.gd @@ -15,7 +15,7 @@ func _physics_process(delta: float) -> void: if not is_on_floor(): velocity.y -= gravity * delta - # Handle Jump. + # Handle jump. if Input.is_action_just_pressed("ui_accept") and is_on_floor(): velocity.y = JUMP_VELOCITY diff --git a/modules/gdscript/editor/script_templates/EditorPlugin/plugin.gd b/modules/gdscript/editor/script_templates/EditorPlugin/plugin.gd index b27b3e5655..547943b910 100644 --- a/modules/gdscript/editor/script_templates/EditorPlugin/plugin.gd +++ b/modules/gdscript/editor/script_templates/EditorPlugin/plugin.gd @@ -1,6 +1,7 @@ # meta-description: Basic plugin template + @tool -extends EditorPlugin +extends _BASE_ func _enter_tree() -> void: diff --git a/modules/gdscript/editor/script_templates/EditorScenePostImport/basic_import_script.gd b/modules/gdscript/editor/script_templates/EditorScenePostImport/basic_import_script.gd index 556afe994b..6772ea4a26 100644 --- a/modules/gdscript/editor/script_templates/EditorScenePostImport/basic_import_script.gd +++ b/modules/gdscript/editor/script_templates/EditorScenePostImport/basic_import_script.gd @@ -1,6 +1,7 @@ # meta-description: Basic import script template + @tool -extends EditorScenePostImport +extends _BASE_ # Called by the editor when a scene has this script set as the import script in the import tab. diff --git a/modules/gdscript/editor/script_templates/EditorScenePostImport/no_comments.gd b/modules/gdscript/editor/script_templates/EditorScenePostImport/no_comments.gd index 875afb4fc0..e8f907f43b 100644 --- a/modules/gdscript/editor/script_templates/EditorScenePostImport/no_comments.gd +++ b/modules/gdscript/editor/script_templates/EditorScenePostImport/no_comments.gd @@ -1,6 +1,7 @@ # meta-description: Basic import script template (no comments) + @tool -extends EditorScenePostImport +extends _BASE_ func _post_import(scene: Node) -> Object: diff --git a/modules/gdscript/editor/script_templates/EditorScript/basic_editor_script.gd b/modules/gdscript/editor/script_templates/EditorScript/basic_editor_script.gd index fdb8550d43..fee7353f0d 100644 --- a/modules/gdscript/editor/script_templates/EditorScript/basic_editor_script.gd +++ b/modules/gdscript/editor/script_templates/EditorScript/basic_editor_script.gd @@ -1,6 +1,7 @@ # meta-description: Basic editor script template + @tool -extends EditorScript +extends _BASE_ # Called when the script is executed (using File -> Run in Script Editor). diff --git a/modules/gdscript/editor/script_templates/RichTextEffect/default.gd b/modules/gdscript/editor/script_templates/RichTextEffect/default.gd index c79eeb91ec..c7a999ef24 100644 --- a/modules/gdscript/editor/script_templates/RichTextEffect/default.gd +++ b/modules/gdscript/editor/script_templates/RichTextEffect/default.gd @@ -1,15 +1,16 @@ # meta-description: Base template for rich text effects @tool -class_name _CLASS_ +# Having a class name is handy for picking the effect in the Inspector. +class_name RichText_CLASS_ extends _BASE_ # To use this effect: # - Enable BBCode on a RichTextLabel. # - Register this effect on the label. -# - Use [_CLASS_ param=2.0]hello[/_CLASS_] in text. -var bbcode := "_CLASS_" +# - Use [_CLASS_SNAKE_CASE_ param=2.0]hello[/_CLASS_SNAKE_CASE_] in text. +var bbcode := "_CLASS_SNAKE_CASE_" func _process_custom_fx(char_fx: CharFXTransform) -> bool: diff --git a/modules/gdscript/editor/script_templates/VisualShaderNodeCustom/basic.gd b/modules/gdscript/editor/script_templates/VisualShaderNodeCustom/basic.gd index 283a95d3b4..458e22dae4 100644 --- a/modules/gdscript/editor/script_templates/VisualShaderNodeCustom/basic.gd +++ b/modules/gdscript/editor/script_templates/VisualShaderNodeCustom/basic.gd @@ -1,6 +1,7 @@ # meta-description: Visual shader's node plugin template @tool +# Having a class name is required for a custom node. class_name VisualShaderNode_CLASS_ extends _BASE_ @@ -17,7 +18,7 @@ func _get_description() -> String: return "" -func _get_return_icon_type() -> int: +func _get_return_icon_type() -> PortType: return PORT_TYPE_SCALAR @@ -29,7 +30,7 @@ func _get_input_port_name(port: int) -> String: return "" -func _get_input_port_type(port: int) -> int: +func _get_input_port_type(port: int) -> PortType: return PORT_TYPE_SCALAR @@ -41,10 +42,10 @@ func _get_output_port_name(port: int) -> String: return "result" -func _get_output_port_type(port: int) -> int: +func _get_output_port_type(port: int) -> PortType: return PORT_TYPE_SCALAR func _get_code(input_vars: Array[String], output_vars: Array[String], - mode: int, type: int) -> String: + mode: Shader.Mode, type: VisualShader.Type) -> String: return output_vars[0] + " = 0.0;" diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index ccbcb3ee96..6245cc85a0 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -83,7 +83,7 @@ void GDScriptNativeClass::_bind_methods() { Variant GDScriptNativeClass::_new() { Object *o = instantiate(); - ERR_FAIL_COND_V_MSG(!o, Variant(), "Class type: '" + String(name) + "' is not instantiable."); + ERR_FAIL_NULL_V_MSG(o, Variant(), "Class type: '" + String(name) + "' is not instantiable."); RefCounted *rc = Object::cast_to<RefCounted>(o); if (rc) { @@ -215,7 +215,7 @@ Variant GDScript::_new(const Variant **p_args, int p_argcount, Callable::CallErr } else { owner = memnew(RefCounted); //by default, no base means use reference } - ERR_FAIL_COND_V_MSG(!owner, Variant(), "Can't inherit from a virtual class."); + ERR_FAIL_NULL_V_MSG(owner, Variant(), "Can't inherit from a virtual class."); RefCounted *r = Object::cast_to<RefCounted>(owner); if (r) { @@ -254,7 +254,7 @@ Ref<Script> GDScript::get_base_script() const { } StringName GDScript::get_global_name() const { - return name; + return global_name; } StringName GDScript::get_instance_base_type() const { @@ -278,33 +278,16 @@ struct _GDScriptMemberSort { void GDScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) { placeholders.erase(p_placeholder); } + #endif void GDScript::_get_script_method_list(List<MethodInfo> *r_list, bool p_include_base) const { const GDScript *current = this; while (current) { for (const KeyValue<StringName, GDScriptFunction *> &E : current->member_functions) { - GDScriptFunction *func = E.value; - MethodInfo mi; - mi.name = E.key; - - if (func->is_static()) { - mi.flags |= METHOD_FLAG_STATIC; - } - - for (int i = 0; i < func->get_argument_count(); i++) { - PropertyInfo arginfo = func->get_argument_type(i); -#ifdef TOOLS_ENABLED - arginfo.name = func->get_argument_name(i); -#endif - mi.arguments.push_back(arginfo); - } -#ifdef TOOLS_ENABLED - mi.default_arguments.append_array(func->get_default_arg_values()); -#endif - mi.return_val = func->get_return_type(); - r_list->push_back(mi); + r_list->push_back(E.value->get_method_info()); } + if (!p_include_base) { return; } @@ -323,10 +306,12 @@ void GDScript::_get_script_property_list(List<PropertyInfo> *r_list, bool p_incl while (sptr) { Vector<_GDScriptMemberSort> msort; - for (const KeyValue<StringName, PropertyInfo> &E : sptr->member_info) { + for (const KeyValue<StringName, MemberInfo> &E : sptr->member_indices) { + if (!sptr->members.has(E.key)) { + continue; // Skip base class members. + } _GDScriptMemberSort ms; - ERR_CONTINUE(!sptr->member_indices.has(E.key)); - ms.index = sptr->member_indices[E.key].index; + ms.index = E.value.index; ms.name = E.key; msort.push_back(ms); } @@ -334,7 +319,7 @@ void GDScript::_get_script_property_list(List<PropertyInfo> *r_list, bool p_incl msort.sort(); msort.reverse(); for (int i = 0; i < msort.size(); i++) { - props.push_front(sptr->member_info[msort[i].name]); + props.push_front(sptr->member_indices[msort[i].name].property_info); } #ifdef TOOLS_ENABLED @@ -362,21 +347,17 @@ bool GDScript::has_method(const StringName &p_method) const { return member_functions.has(p_method); } +bool GDScript::has_static_method(const StringName &p_method) const { + return member_functions.has(p_method) && member_functions[p_method]->is_static(); +} + MethodInfo GDScript::get_method_info(const StringName &p_method) const { HashMap<StringName, GDScriptFunction *>::ConstIterator E = member_functions.find(p_method); if (!E) { return MethodInfo(); } - GDScriptFunction *func = E->value; - MethodInfo mi; - mi.name = E->key; - for (int i = 0; i < func->get_argument_count(); i++) { - mi.arguments.push_back(func->get_argument_type(i)); - } - - mi.return_val = func->get_return_type(); - return mi; + return E->value->get_method_info(); } bool GDScript::get_property_default_value(const StringName &p_property, Variant &r_value) const { @@ -488,7 +469,7 @@ String GDScript::get_class_icon_path() const { } #endif -bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderScriptInstance *p_instance_to_update) { +bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderScriptInstance *p_instance_to_update, bool p_base_exports_changed) { #ifdef TOOLS_ENABLED static Vector<GDScript *> base_caches; @@ -497,7 +478,7 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc } base_caches.append(this); - bool changed = false; + bool changed = p_base_exports_changed; if (source_changed_cache) { source_changed_cache = false; @@ -557,13 +538,7 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc member_default_values_cache[member.variable->identifier->name] = default_value; } break; case GDScriptParser::ClassNode::Member::SIGNAL: { - // TODO: Cache this in parser to avoid loops like this. - Vector<StringName> parameters_names; - parameters_names.resize(member.signal->parameters.size()); - for (int j = 0; j < member.signal->parameters.size(); j++) { - parameters_names.write[j] = member.signal->parameters[j]->identifier->name; - } - _signals[member.signal->identifier->name] = parameters_names; + _signals[member.signal->identifier->name] = member.signal->method_info; } break; case GDScriptParser::ClassNode::Member::GROUP: { members_cache.push_back(member.annotation->export_info); @@ -630,9 +605,15 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc void GDScript::update_exports() { #ifdef TOOLS_ENABLED + _update_exports_down(false); +#endif +} +#ifdef TOOLS_ENABLED +void GDScript::_update_exports_down(bool p_base_exports_changed) { bool cyclic_error = false; - _update_exports(&cyclic_error); + bool changed = _update_exports(&cyclic_error, false, nullptr, p_base_exports_changed); + if (cyclic_error) { return; } @@ -642,14 +623,14 @@ void GDScript::update_exports() { for (const ObjectID &E : copy) { Object *id = ObjectDB::get_instance(E); GDScript *s = Object::cast_to<GDScript>(id); + if (!s) { continue; } - s->update_exports(); + s->_update_exports_down(p_base_exports_changed || changed); } - -#endif } +#endif String GDScript::_get_debug_path() const { if (is_built_in() && !get_name().is_empty()) { @@ -977,22 +958,26 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) { void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const { p_properties->push_back(PropertyInfo(Variant::STRING, "script/source", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); - List<PropertyInfo> property_list; - + List<const GDScript *> classes; const GDScript *top = this; while (top) { - for (const KeyValue<StringName, MemberInfo> &E : top->static_variables_indices) { - PropertyInfo pi = PropertyInfo(E.value.data_type); - pi.name = E.key; - pi.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; // For the script (as a class) it is a non-static property. - property_list.push_back(pi); - } - + classes.push_back(top); top = top->_base; } - for (const List<PropertyInfo>::Element *E = property_list.back(); E; E = E->prev()) { - p_properties->push_back(E->get()); + for (const List<const GDScript *>::Element *E = classes.back(); E; E = E->prev()) { + Vector<_GDScriptMemberSort> msort; + for (const KeyValue<StringName, MemberInfo> &F : E->get()->static_variables_indices) { + _GDScriptMemberSort ms; + ms.index = F.value.index; + ms.name = F.key; + msort.push_back(ms); + } + msort.sort(); + + for (int i = 0; i < msort.size(); i++) { + p_properties->push_back(E->get()->static_variables_indices[msort[i].name].property_info); + } } } @@ -1110,7 +1095,7 @@ GDScript *GDScript::find_class(const String &p_qualified_name) { Vector<String> class_names; GDScript *result = nullptr; // Empty initial name means start here. - if (first.is_empty() || first == name) { + if (first.is_empty() || first == global_name) { class_names = p_qualified_name.split("::"); result = this; } else if (p_qualified_name.begins_with(get_root_script()->path)) { @@ -1245,15 +1230,8 @@ bool GDScript::has_script_signal(const StringName &p_signal) const { } void GDScript::_get_script_signal_list(List<MethodInfo> *r_list, bool p_include_base) const { - for (const KeyValue<StringName, Vector<StringName>> &E : _signals) { - MethodInfo mi; - mi.name = E.key; - for (int i = 0; i < E.value.size(); i++) { - PropertyInfo arg; - arg.name = E.value[i]; - mi.arguments.push_back(arg); - } - r_list->push_back(mi); + for (const KeyValue<StringName, MethodInfo> &E : _signals) { + r_list->push_back(E.value); } if (!p_include_base) { @@ -1274,21 +1252,6 @@ void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const { _get_script_signal_list(r_signals, true); } -String GDScript::_get_gdscript_reference_class_name(const GDScript *p_gdscript) { - ERR_FAIL_NULL_V(p_gdscript, String()); - - String class_name; - while (p_gdscript) { - if (class_name.is_empty()) { - class_name = p_gdscript->get_script_class_name(); - } else { - class_name = p_gdscript->get_script_class_name() + "." + class_name; - } - p_gdscript = p_gdscript->_owner; - } - return class_name; -} - GDScript *GDScript::_get_gdscript_from_variant(const Variant &p_variant) { Object *obj = p_variant; if (obj == nullptr || obj->get_instance_id().is_null()) { @@ -1399,29 +1362,13 @@ void GDScript::_save_orphaned_subclasses(ClearData *p_clear_data) { } } -void GDScript::_init_rpc_methods_properties() { - // Copy the base rpc methods so we don't mask their IDs. - rpc_config.clear(); - if (base.is_valid()) { - rpc_config = base->rpc_config.duplicate(); - } - - // RPC Methods - for (KeyValue<StringName, GDScriptFunction *> &E : member_functions) { - Variant config = E.value->get_rpc_config(); - if (config.get_type() != Variant::NIL) { - rpc_config[E.value->get_name()] = config; - } - } -} - #ifdef DEBUG_ENABLED String GDScript::debug_get_script_name(const Ref<Script> &p_script) { if (p_script.is_valid()) { Ref<GDScript> gdscript = p_script; if (gdscript.is_valid()) { - if (!gdscript->get_script_class_name().is_empty()) { - return gdscript->get_script_class_name(); + if (gdscript->get_local_name() != StringName()) { + return gdscript->get_local_name(); } return gdscript->get_fully_qualified_name().get_file(); } @@ -1439,6 +1386,36 @@ String GDScript::debug_get_script_name(const Ref<Script> &p_script) { } #endif +thread_local GDScript::UpdatableFuncPtr GDScript::func_ptrs_to_update_thread_local; + +GDScript::UpdatableFuncPtrElement GDScript::_add_func_ptr_to_update(GDScriptFunction **p_func_ptr_ptr) { + UpdatableFuncPtrElement result = {}; + + { + MutexLock lock(func_ptrs_to_update_thread_local.mutex); + result.element = func_ptrs_to_update_thread_local.ptrs.push_back(p_func_ptr_ptr); + result.mutex = &func_ptrs_to_update_thread_local.mutex; + + if (likely(func_ptrs_to_update_thread_local.initialized)) { + return result; + } + + func_ptrs_to_update_thread_local.initialized = true; + } + + MutexLock lock(func_ptrs_to_update_mutex); + func_ptrs_to_update.push_back(&func_ptrs_to_update_thread_local); + + return result; +} + +void GDScript::_remove_func_ptr_to_update(const UpdatableFuncPtrElement p_func_ptr_element) { + ERR_FAIL_NULL(p_func_ptr_element.element); + ERR_FAIL_NULL(p_func_ptr_element.mutex); + MutexLock lock(*p_func_ptr_element.mutex); + p_func_ptr_element.element->erase(); +} + void GDScript::clear(ClearData *p_clear_data) { if (clearing) { return; @@ -1456,6 +1433,16 @@ void GDScript::clear(ClearData *p_clear_data) { is_root = true; } + { + MutexLock outer_lock(func_ptrs_to_update_mutex); + for (UpdatableFuncPtr *updatable : func_ptrs_to_update) { + MutexLock inner_lock(updatable->mutex); + for (GDScriptFunction **func_ptr_ptr : updatable->ptrs) { + *func_ptr_ptr = nullptr; + } + } + } + RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies(); for (GDScript *E : must_clear_dependencies) { clear_data->scripts.insert(E); @@ -1667,7 +1654,7 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { } { - HashMap<StringName, Vector<StringName>>::ConstIterator E = sptr->_signals.find(p_name); + HashMap<StringName, MethodInfo>::ConstIterator E = sptr->_signals.find(p_name); if (E) { r_ret = Signal(this->owner, E->key); return true; @@ -1715,15 +1702,11 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { } Variant::Type GDScriptInstance::get_property_type(const StringName &p_name, bool *r_is_valid) const { - const GDScript *sptr = script.ptr(); - while (sptr) { - if (sptr->member_info.has(p_name)) { - if (r_is_valid) { - *r_is_valid = true; - } - return sptr->member_info[p_name].type; + if (script->member_indices.has(p_name)) { + if (r_is_valid) { + *r_is_valid = true; } - sptr = sptr->_base; + return script->member_indices[p_name].property_info.type; } if (r_is_valid) { @@ -1798,10 +1781,12 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const //instance a fake script for editing the values Vector<_GDScriptMemberSort> msort; - for (const KeyValue<StringName, PropertyInfo> &F : sptr->member_info) { + for (const KeyValue<StringName, GDScript::MemberInfo> &F : sptr->member_indices) { + if (!sptr->members.has(F.key)) { + continue; // Skip base class members. + } _GDScriptMemberSort ms; - ERR_CONTINUE(!sptr->member_indices.has(F.key)); - ms.index = sptr->member_indices[F.key].index; + ms.index = F.value.index; ms.name = F.key; msort.push_back(ms); } @@ -1809,7 +1794,7 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const msort.sort(); msort.reverse(); for (int i = 0; i < msort.size(); i++) { - props.push_front(sptr->member_info[msort[i].name]); + props.push_front(sptr->member_indices[msort[i].name].property_info); } #ifdef TOOLS_ENABLED @@ -1872,12 +1857,7 @@ void GDScriptInstance::get_method_list(List<MethodInfo> *p_list) const { const GDScript *sptr = script.ptr(); while (sptr) { for (const KeyValue<StringName, GDScriptFunction *> &E : sptr->member_functions) { - MethodInfo mi; - mi.name = E.key; - for (int i = 0; i < E.value->get_argument_count(); i++) { - mi.arguments.push_back(PropertyInfo(Variant::NIL, "arg" + itos(i))); - } - p_list->push_back(mi); + p_list->push_back(E.value->get_method_info()); } sptr = sptr->_base; } @@ -2439,61 +2419,60 @@ void GDScriptLanguage::frame() { /* EDITOR FUNCTIONS */ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { - // TODO: Add annotations here? + // Please keep alphabetical order within categories. static const char *_reserved_words[] = { - // operators + // Control flow. + "break", + "continue", + "elif", + "else", + "for", + "if", + "match", + "pass", + "return", + "when", + "while", + // Declarations. + "class", + "class_name", + "const", + "enum", + "extends", + "func", + "namespace", // Reserved for potential future use. + "signal", + "static", + "trait", // Reserved for potential future use. + "var", + // Other keywords. + "await", + "breakpoint", + "self", + "super", + "yield", // Reserved for potential future use. + // Operators. "and", + "as", "in", + "is", "not", "or", - // types and values + // Special values (tokenizer treats them as literals, not as tokens). "false", - "float", - "int", - "bool", "null", - "PI", - "TAU", + "true", + // Constants. "INF", "NAN", - "self", - "true", - "void", - // functions - "as", + "PI", + "TAU", + // Functions (highlighter uses global function color instead). "assert", - "await", - "breakpoint", - "class", - "class_name", - "extends", - "is", - "func", "preload", - "signal", - "super", - // var - "const", - "enum", - "static", - "var", - // control flow - "break", - "continue", - "if", - "elif", - "else", - "for", - "pass", - "return", - "match", - "while", - // These keywords are not implemented currently, but reserved for (potential) future use. - // We highlight them as keywords to make errors easier to understand. - "trait", - "namespace", - "yield", - nullptr + // Types (highlighter uses type color instead). + "void", + nullptr, }; const char **w = _reserved_words; @@ -2502,25 +2481,20 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { p_words->push_back(*w); w++; } - - List<StringName> functions; - GDScriptUtilityFunctions::get_function_list(&functions); - - for (const StringName &E : functions) { - p_words->push_back(String(E)); - } } bool GDScriptLanguage::is_control_flow_keyword(String p_keyword) const { + // Please keep alphabetical order. return p_keyword == "break" || p_keyword == "continue" || p_keyword == "elif" || p_keyword == "else" || - p_keyword == "if" || p_keyword == "for" || + p_keyword == "if" || p_keyword == "match" || p_keyword == "pass" || p_keyword == "return" || + p_keyword == "when" || p_keyword == "while"; } diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 0ba007683c..04b0a1d786 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -69,6 +69,7 @@ class GDScript : public Script { StringName setter; StringName getter; GDScriptDataType data_type; + PropertyInfo property_info; }; struct ClearData { @@ -85,6 +86,8 @@ class GDScript : public Script { friend class GDScriptAnalyzer; friend class GDScriptCompiler; friend class GDScriptDocGen; + friend class GDScriptLambdaCallable; + friend class GDScriptLambdaSelfCallable; friend class GDScriptLanguage; friend struct GDScriptUtilityFunctionsDefinitions; @@ -93,16 +96,44 @@ class GDScript : public Script { GDScript *_base = nullptr; //fast pointer access GDScript *_owner = nullptr; //for subclasses - HashSet<StringName> members; //members are just indices to the instantiated script. - HashMap<StringName, Variant> constants; + // Members are just indices to the instantiated script. + HashMap<StringName, MemberInfo> member_indices; // Includes member info of all base GDScript classes. + HashSet<StringName> members; // Only members of the current class. + + // Only static variables of the current class. HashMap<StringName, MemberInfo> static_variables_indices; - Vector<Variant> static_variables; + Vector<Variant> static_variables; // Static variable values. + + HashMap<StringName, Variant> constants; HashMap<StringName, GDScriptFunction *> member_functions; - HashMap<StringName, MemberInfo> member_indices; //members are just indices to the instantiated script. HashMap<StringName, Ref<GDScript>> subclasses; - HashMap<StringName, Vector<StringName>> _signals; + HashMap<StringName, MethodInfo> _signals; Dictionary rpc_config; + struct LambdaInfo { + int capture_count; + bool use_self; + }; + + HashMap<GDScriptFunction *, LambdaInfo> lambda_info; + + // List is used here because a ptr to elements are stored, so the memory locations need to be stable + struct UpdatableFuncPtr { + List<GDScriptFunction **> ptrs; + Mutex mutex; + bool initialized = false; + }; + struct UpdatableFuncPtrElement { + List<GDScriptFunction **>::Element *element = nullptr; + Mutex *mutex = nullptr; + }; + static thread_local UpdatableFuncPtr func_ptrs_to_update_thread_local; + List<UpdatableFuncPtr *> func_ptrs_to_update; + Mutex func_ptrs_to_update_mutex; + + UpdatableFuncPtrElement _add_func_ptr_to_update(GDScriptFunction **p_func_ptr_ptr); + static void _remove_func_ptr_to_update(const UpdatableFuncPtrElement p_func_ptr_element); + #ifdef TOOLS_ENABLED // For static data storage during hot-reloading. HashMap<StringName, MemberInfo> old_static_variables_indices; @@ -126,8 +157,6 @@ class GDScript : public Script { void _add_doc(const DocData::ClassDoc &p_inner_class); #endif - HashMap<StringName, PropertyInfo> member_info; - GDScriptFunction *implicit_initializer = nullptr; GDScriptFunction *initializer = nullptr; //direct pointer to new , faster to locate GDScriptFunction *implicit_ready = nullptr; @@ -142,7 +171,8 @@ class GDScript : public Script { //exported members String source; String path; - String name; + StringName local_name; // Inner class identifier or `class_name`. + StringName global_name; // `class_name`. String fully_qualified_name; String simplified_icon_path; SelfList<GDScript> script_list; @@ -159,24 +189,21 @@ class GDScript : public Script { HashSet<PlaceHolderScriptInstance *> placeholders; //void _update_placeholder(PlaceHolderScriptInstance *p_placeholder); virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder) override; + void _update_exports_down(bool p_base_exports_changed); #endif #ifdef DEBUG_ENABLED HashMap<ObjectID, List<Pair<StringName, Variant>>> pending_reload_state; #endif - bool _update_exports(bool *r_err = nullptr, bool p_recursive_call = false, PlaceHolderScriptInstance *p_instance_to_update = nullptr); + bool _update_exports(bool *r_err = nullptr, bool p_recursive_call = false, PlaceHolderScriptInstance *p_instance_to_update = nullptr, bool p_base_exports_changed = false); void _save_orphaned_subclasses(GDScript::ClearData *p_clear_data); - void _init_rpc_methods_properties(); void _get_script_property_list(List<PropertyInfo> *r_list, bool p_include_base) const; 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 "RefCounted" to "MyClass.InnerClass". - static String _get_gdscript_reference_class_name(const GDScript *p_gdscript); - GDScript *_get_gdscript_from_variant(const Variant &p_variant); void _get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except); @@ -194,9 +221,12 @@ public: static String debug_get_script_name(const Ref<Script> &p_script); #endif + _FORCE_INLINE_ StringName get_local_name() const { return local_name; } + void clear(GDScript::ClearData *p_clear_data = nullptr); virtual bool is_valid() const override { return valid; } + virtual bool is_abstract() const override { return false; } // GDScript does not support abstract classes. bool inherits_script(const Ref<Script> &p_script) const override; @@ -214,7 +244,6 @@ public: } const HashMap<StringName, GDScriptFunction *> &get_member_functions() const { return member_functions; } const Ref<GDScriptNativeClass> &get_native() const { return native; } - const String &get_script_class_name() const { return name; } RBSet<GDScript *> get_dependencies(); RBSet<GDScript *> get_inverted_dependencies(); @@ -264,6 +293,7 @@ public: virtual void get_script_method_list(List<MethodInfo> *p_list) const override; virtual bool has_method(const StringName &p_method) const override; + virtual bool has_static_method(const StringName &p_method) const override; virtual MethodInfo get_method_info(const StringName &p_method) const override; virtual void get_script_property_list(List<PropertyInfo> *p_list) const override; @@ -498,13 +528,16 @@ public: virtual void get_reserved_words(List<String> *p_words) const override; virtual bool is_control_flow_keyword(String p_keywords) const override; virtual void get_comment_delimiters(List<String> *p_delimiters) const override; + virtual void get_doc_comment_delimiters(List<String> *p_delimiters) const override; virtual void get_string_delimiters(List<String> *p_delimiters) const override; virtual bool is_using_templates() override; virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const override; virtual Vector<ScriptTemplate> get_built_in_templates(StringName p_object) override; 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, HashSet<int> *r_safe_lines = nullptr) const override; virtual Script *create_script() const override; - virtual bool has_named_classes() const override; +#ifndef DISABLE_DEPRECATED + virtual bool has_named_classes() const override { return false; } +#endif virtual bool supports_builtin_mode() const override; virtual bool supports_documentation() const override; virtual bool can_inherit_from_file() const override { return true; } diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 18c69467dc..983a19470a 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -248,7 +248,7 @@ Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_me return ERR_PARSE_ERROR; } - if (GDScriptParser::get_builtin_type(p_member_name) != Variant::VARIANT_MAX) { + if (GDScriptParser::get_builtin_type(p_member_name) < Variant::VARIANT_MAX) { push_error(vformat(R"(The member "%s" cannot have the same name as a builtin type.)", p_member_name), p_member_node); return ERR_PARSE_ERROR; } @@ -386,6 +386,7 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c if (!p_class->extends_used) { result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; result.kind = GDScriptParser::DataType::NATIVE; + result.builtin_type = Variant::OBJECT; result.native_type = SNAME("RefCounted"); } else { result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; @@ -459,7 +460,12 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c } base = info_parser->get_parser()->head->get_datatype(); } else if (class_exists(name)) { + if (Engine::get_singleton()->has_singleton(name)) { + push_error(vformat(R"(Cannot inherit native class "%s" because it is an engine singleton.)", name), id); + return ERR_PARSE_ERROR; + } base.kind = GDScriptParser::DataType::NATIVE; + base.builtin_type = Variant::OBJECT; base.native_type = name; } else { // Look for other classes in script. @@ -669,11 +675,6 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return bad_type; } result.kind = GDScriptParser::DataType::VARIANT; - } else if (first == SNAME("Object")) { - // Object is treated like a native type, not a built-in. - result.kind = GDScriptParser::DataType::NATIVE; - result.builtin_type = Variant::OBJECT; - result.native_type = SNAME("Object"); } else if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) { // Built-in types. if (p_type->type_chain.size() > 1) { @@ -909,7 +910,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, for (GDScriptParser::AnnotationNode *&E : member_node->annotations) { if (E->name == SNAME("@warning_ignore")) { resolve_annotation(E); - E->apply(parser, member.variable); + E->apply(parser, member.variable, p_class); } } for (GDScriptWarning::Code ignored_warning : member_node->ignored_warnings) { @@ -932,7 +933,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, for (GDScriptParser::AnnotationNode *&E : member.variable->annotations) { if (E->name != SNAME("@warning_ignore")) { resolve_annotation(E); - E->apply(parser, member.variable); + E->apply(parser, member.variable, p_class); } } @@ -984,7 +985,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.constant->annotations) { resolve_annotation(E); - E->apply(parser, member.constant); + E->apply(parser, member.constant, p_class); } } break; case GDScriptParser::ClassNode::Member::SIGNAL: { @@ -1000,15 +1001,21 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, GDScriptParser::ParameterNode *param = member.signal->parameters[j]; GDScriptParser::DataType param_type = type_from_metatype(resolve_datatype(param->datatype_specifier)); param->set_datatype(param_type); - mi.arguments.push_back(PropertyInfo(param_type.builtin_type, param->identifier->name)); - // TODO: add signal parameter default values +#ifdef DEBUG_ENABLED + if (param->datatype_specifier == nullptr) { + parser->push_warning(param, GDScriptWarning::UNTYPED_DECLARATION, "Parameter", param->identifier->name); + } +#endif + mi.arguments.push_back(param_type.to_property_info(param->identifier->name)); + // Signals do not support parameter default values. } member.signal->set_datatype(make_signal_type(mi)); + member.signal->method_info = mi; // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.signal->annotations) { resolve_annotation(E); - E->apply(parser, member.signal); + E->apply(parser, member.signal, p_class); } } break; case GDScriptParser::ClassNode::Member::ENUM: { @@ -1056,13 +1063,13 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.m_enum->annotations) { resolve_annotation(E); - E->apply(parser, member.m_enum); + E->apply(parser, member.m_enum, p_class); } } break; case GDScriptParser::ClassNode::Member::FUNCTION: for (GDScriptParser::AnnotationNode *&E : member.function->annotations) { resolve_annotation(E); - E->apply(parser, member.function); + E->apply(parser, member.function, p_class); } resolve_function_signature(member.function, p_source); break; @@ -1272,23 +1279,21 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.function->annotations) { resolve_annotation(E); - E->apply(parser, member.function); + E->apply(parser, member.function, p_class); } resolve_function_body(member.function); } else if (member.type == GDScriptParser::ClassNode::Member::VARIABLE && member.variable->property != GDScriptParser::VariableNode::PROP_NONE) { if (member.variable->property == GDScriptParser::VariableNode::PROP_INLINE) { if (member.variable->getter != nullptr) { - member.variable->getter->set_datatype(member.variable->datatype); + member.variable->getter->return_type = member.variable->datatype_specifier; + member.variable->getter->set_datatype(member.get_datatype()); resolve_function_body(member.variable->getter); } if (member.variable->setter != nullptr) { - resolve_function_signature(member.variable->setter); - - if (member.variable->setter->parameters.size() > 0) { - member.variable->setter->parameters[0]->datatype_specifier = member.variable->datatype_specifier; - member.variable->setter->parameters[0]->set_datatype(member.get_datatype()); - } + ERR_CONTINUE(member.variable->setter->parameters.is_empty()); + member.variable->setter->parameters[0]->datatype_specifier = member.variable->datatype_specifier; + member.variable->setter->parameters[0]->set_datatype(member.get_datatype()); resolve_function_body(member.variable->setter); } @@ -1296,7 +1301,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co } else if (member.type == GDScriptParser::ClassNode::Member::GROUP) { // Apply annotation (`@export_{category,group,subgroup}`). resolve_annotation(member.annotation); - member.annotation->apply(parser, nullptr); + member.annotation->apply(parser, nullptr, p_class); } } @@ -1411,7 +1416,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, bo } void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root) { - ERR_FAIL_COND_MSG(p_node == nullptr, "Trying to resolve type of a null node."); + ERR_FAIL_NULL_MSG(p_node, "Trying to resolve type of a null node."); switch (p_node->type) { case GDScriptParser::Node::NONE: @@ -1580,7 +1585,13 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * GDScriptParser::FunctionNode *previous_function = parser->current_function; parser->current_function = p_function; bool previous_static_context = static_context; - static_context = p_function->is_static; + if (p_is_lambda) { + // For lambdas this is determined from the context, the `static` keyword is not allowed. + p_function->is_static = static_context; + } else { + // For normal functions, this is determined in the parser by the `static` keyword. + static_context = p_function->is_static; + } GDScriptParser::DataType prev_datatype = p_function->get_datatype(); @@ -1592,21 +1603,26 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * int default_value_count = 0; #endif // TOOLS_ENABLED +#ifdef DEBUG_ENABLED + String function_visible_name = function_name; + if (function_name == StringName()) { + function_visible_name = p_is_lambda ? "<anonymous lambda>" : "<unknown function>"; + } +#endif + for (int i = 0; i < p_function->parameters.size(); i++) { resolve_parameter(p_function->parameters[i]); #ifdef DEBUG_ENABLED if (p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_")) { - String visible_name = function_name; - if (function_name == StringName()) { - visible_name = p_is_lambda ? "<anonymous lambda>" : "<unknown function>"; - } - parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, visible_name, p_function->parameters[i]->identifier->name); + parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, function_visible_name, p_function->parameters[i]->identifier->name); } is_shadowing(p_function->parameters[i]->identifier, "function parameter", true); #endif // DEBUG_ENABLED -#ifdef TOOLS_ENABLED + if (p_function->parameters[i]->initializer) { +#ifdef TOOLS_ENABLED default_value_count++; +#endif // TOOLS_ENABLED if (p_function->parameters[i]->initializer->is_constant) { p_function->default_arg_values.push_back(p_function->parameters[i]->initializer->reduced_value); @@ -1614,7 +1630,6 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * p_function->default_arg_values.push_back(Variant()); // Prevent shift. } } -#endif // TOOLS_ENABLED } if (!p_is_lambda && function_name == GDScriptLanguage::get_singleton()->strings._init) { @@ -1664,15 +1679,42 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * StringName native_base; if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, method_flags, &native_base)) { bool valid = p_function->is_static == method_flags.has_flag(METHOD_FLAG_STATIC); - valid = valid && parent_return_type == p_function->get_datatype(); + + if (p_function->return_type != nullptr) { + // Check return type covariance. + GDScriptParser::DataType return_type = p_function->get_datatype(); + if (return_type.is_variant()) { + // `is_type_compatible()` returns `true` if one of the types is `Variant`. + // Don't allow an explicitly specified `Variant` if the parent return type is narrower. + valid = valid && parent_return_type.is_variant(); + } else if (return_type.kind == GDScriptParser::DataType::BUILTIN && return_type.builtin_type == Variant::NIL) { + // `is_type_compatible()` returns `true` if target is an `Object` and source is `null`. + // Don't allow `void` if the parent return type is a hard non-`void` type. + if (parent_return_type.is_hard_type() && !(parent_return_type.kind == GDScriptParser::DataType::BUILTIN && parent_return_type.builtin_type == Variant::NIL)) { + valid = false; + } + } else { + valid = valid && is_type_compatible(parent_return_type, return_type); + } + } int par_count_diff = p_function->parameters.size() - parameters_types.size(); valid = valid && par_count_diff >= 0; valid = valid && default_value_count >= default_par_count + par_count_diff; - int i = 0; - for (const GDScriptParser::DataType &par_type : parameters_types) { - valid = valid && par_type == p_function->parameters[i++]->get_datatype(); + if (valid) { + int i = 0; + for (const GDScriptParser::DataType &parent_par_type : parameters_types) { + // Check parameter type contravariance. + GDScriptParser::DataType current_par_type = p_function->parameters[i++]->get_datatype(); + if (parent_par_type.is_variant() && parent_par_type.is_hard_type()) { + // `is_type_compatible()` returns `true` if one of the types is `Variant`. + // Don't allow narrowing a hard `Variant`. + valid = valid && current_par_type.is_variant(); + } else { + valid = valid && is_type_compatible(current_par_type, parent_par_type); + } + } } if (!valid) { @@ -1696,7 +1738,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * } parent_signature += ") -> "; - const String return_type = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant"; + const String return_type = parent_return_type.to_string_strict(); if (return_type == "null") { parent_signature += "void"; } else { @@ -1714,6 +1756,12 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * #endif // TOOLS_ENABLED } +#ifdef DEBUG_ENABLED + if (p_function->return_type == nullptr) { + parser->push_warning(p_function, GDScriptWarning::UNTYPED_DECLARATION, "Function", function_visible_name); + } +#endif + if (p_function->get_datatype().is_resolving()) { p_function->set_datatype(prev_datatype); } @@ -1795,7 +1843,7 @@ void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) { // Apply annotations. for (GDScriptParser::AnnotationNode *&E : stmt->annotations) { resolve_annotation(E); - E->apply(parser, stmt); + E->apply(parser, stmt, nullptr); } #ifdef DEBUG_ENABLED @@ -1917,6 +1965,18 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } } +#ifdef DEBUG_ENABLED + if (!has_specified_type) { + const bool is_parameter = p_assignable->type == GDScriptParser::Node::PARAMETER; + const String declaration_type = is_constant ? "Constant" : (is_parameter ? "Parameter" : "Variable"); + if (p_assignable->infer_datatype || is_constant) { + parser->push_warning(p_assignable, GDScriptWarning::INFERRED_DECLARATION, declaration_type, p_assignable->identifier->name); + } else { + parser->push_warning(p_assignable, GDScriptWarning::UNTYPED_DECLARATION, declaration_type, p_assignable->identifier->name); + } + } +#endif + type.is_constant = is_constant; type.is_read_only = false; p_assignable->set_datatype(type); @@ -2121,19 +2181,21 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { } } else if (!is_type_compatible(specified_type, variable_type)) { p_for->use_conversion_assign = true; -#ifdef DEBUG_ENABLED - } else { - parser->push_warning(p_for->datatype_specifier, GDScriptWarning::REDUNDANT_FOR_VARIABLE_TYPE, p_for->variable->name, variable_type.to_string(), specified_type.to_string()); -#endif } -#ifdef DEBUG_ENABLED - } else { - parser->push_warning(p_for->datatype_specifier, GDScriptWarning::REDUNDANT_FOR_VARIABLE_TYPE, p_for->variable->name, variable_type.to_string(), specified_type.to_string()); -#endif + if (p_for->list && p_for->list->type == GDScriptParser::Node::ARRAY) { + update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type); + } } p_for->variable->set_datatype(specified_type); } else { p_for->variable->set_datatype(variable_type); +#ifdef DEBUG_ENABLED + if (variable_type.is_hard_type()) { + parser->push_warning(p_for->variable, GDScriptWarning::INFERRED_DECLARATION, R"("for" iterator variable)", p_for->variable->name); + } else { + parser->push_warning(p_for->variable, GDScriptWarning::UNTYPED_DECLARATION, R"("for" iterator variable)", p_for->variable->name); + } +#endif } } @@ -2190,6 +2252,10 @@ void GDScriptAnalyzer::resolve_match_branch(GDScriptParser::MatchBranchNode *p_m resolve_match_pattern(p_match_branch->patterns[i], p_match_test); } + if (p_match_branch->guard_body) { + resolve_suite(p_match_branch->guard_body); + } + resolve_suite(p_match_branch->block); decide_suite_type(p_match_branch, p_match_branch->block); @@ -2440,6 +2506,14 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre case GDScriptParser::Node::WHILE: ERR_FAIL_MSG("Reaching unreachable case"); } + + if (p_expression->get_datatype().kind == GDScriptParser::DataType::UNRESOLVED) { + // Prevent `is_type_compatible()` errors for incomplete expressions. + // The error can still occur if `reduce_*()` is called directly. + GDScriptParser::DataType dummy; + dummy.kind = GDScriptParser::DataType::VARIANT; + p_expression->set_datatype(dummy); + } } void GDScriptAnalyzer::reduce_array(GDScriptParser::ArrayNode *p_array) { @@ -2523,28 +2597,31 @@ void GDScriptAnalyzer::update_const_expression_builtin_type(GDScriptParser::Expr // 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(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type) { + GDScriptParser::DataType expected_type = p_element_type; + expected_type.unset_container_element_type(); // Nested types (like `Array[Array[int]]`) are not currently supported. + for (int i = 0; i < p_array->elements.size(); i++) { GDScriptParser::ExpressionNode *element_node = p_array->elements[i]; if (element_node->is_constant) { - update_const_expression_builtin_type(element_node, p_element_type, "include"); + update_const_expression_builtin_type(element_node, expected_type, "include"); } - const GDScriptParser::DataType &element_type = element_node->get_datatype(); - if (element_type.has_no_type() || element_type.is_variant() || !element_type.is_hard_type()) { + const GDScriptParser::DataType &actual_type = element_node->get_datatype(); + if (actual_type.has_no_type() || actual_type.is_variant() || !actual_type.is_hard_type()) { mark_node_unsafe(element_node); continue; } - if (!is_type_compatible(p_element_type, element_type, true, p_array)) { - if (is_type_compatible(element_type, p_element_type)) { + if (!is_type_compatible(expected_type, actual_type, true, p_array)) { + if (is_type_compatible(actual_type, expected_type)) { mark_node_unsafe(element_node); continue; } - push_error(vformat(R"(Cannot have an element of type "%s" in an array of type "Array[%s]".)", element_type.to_string(), p_element_type.to_string()), element_node); + push_error(vformat(R"(Cannot have an element of type "%s" in an array of type "Array[%s]".)", actual_type.to_string(), expected_type.to_string()), element_node); return; } } GDScriptParser::DataType array_type = p_array->get_datatype(); - array_type.set_container_element_type(p_element_type); + array_type.set_container_element_type(expected_type); p_array->set_datatype(array_type); } @@ -2591,7 +2668,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig } // Check if assigned value is an array literal, so we can make it a typed array too if appropriate. - if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.has_container_element_type()) { + if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.is_hard_type() && assignee_type.has_container_element_type()) { update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type()); } @@ -2870,19 +2947,20 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a if (!p_call->is_super && callee_type == GDScriptParser::Node::IDENTIFIER) { // Call to name directly. StringName function_name = p_call->function_name; - Variant::Type builtin_type = GDScriptParser::get_builtin_type(function_name); + if (function_name == SNAME("Object")) { + push_error(R"*(Invalid constructor "Object()", use "Object.new()" instead.)*", p_call); + p_call->set_datatype(call_type); + return; + } + + Variant::Type builtin_type = GDScriptParser::get_builtin_type(function_name); if (builtin_type < Variant::VARIANT_MAX) { // Is a builtin constructor. call_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; call_type.kind = GDScriptParser::DataType::BUILTIN; call_type.builtin_type = builtin_type; - if (builtin_type == Variant::OBJECT) { - call_type.kind = GDScriptParser::DataType::NATIVE; - call_type.native_type = function_name; // "Object". - } - bool safe_to_fold = true; switch (builtin_type) { // Those are stored by reference so not suited for compile-time construction. @@ -2918,7 +2996,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a switch (err.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: - push_error(vformat(R"(Invalid argument for %s constructor: argument %d should be "%s" but is "%s".)", Variant::get_type_name(builtin_type), err.argument + 1, + push_error(vformat(R"*(Invalid argument for "%s()" constructor: argument %d should be "%s" but is "%s".)*", Variant::get_type_name(builtin_type), err.argument + 1, Variant::get_type_name(Variant::Type(err.expected)), p_call->arguments[err.argument]->get_datatype().to_string()), p_call->arguments[err.argument]); break; @@ -2934,10 +3012,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call->callee); } break; case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: - push_error(vformat(R"(Too many arguments for %s constructor. Received %d but expected %d.)", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); + push_error(vformat(R"*(Too many arguments for "%s()" constructor. Received %d but expected %d.)*", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); break; case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: - push_error(vformat(R"(Too few arguments for %s constructor. Received %d but expected %d.)", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); + push_error(vformat(R"*(Too few arguments for "%s()" constructor. Received %d but expected %d.)*", Variant::get_type_name(builtin_type), p_call->arguments.size(), err.expected), p_call); break; case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST: @@ -2948,21 +3026,41 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a break; } } else { - // TODO: Check constructors without constants. - // If there's one argument, try to use copy constructor (those aren't explicitly defined). if (p_call->arguments.size() == 1) { GDScriptParser::DataType arg_type = p_call->arguments[0]->get_datatype(); - if (arg_type.is_variant()) { - mark_node_unsafe(p_call->arguments[0]); - } else { + if (arg_type.is_hard_type() && !arg_type.is_variant()) { if (arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == builtin_type) { // Okay. p_call->set_datatype(call_type); return; } + } else { +#ifdef DEBUG_ENABLED + mark_node_unsafe(p_call); + // Constructors support overloads. + Vector<String> types; + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + if (i != builtin_type && Variant::can_convert_strict((Variant::Type)i, builtin_type)) { + types.push_back(Variant::get_type_name((Variant::Type)i)); + } + } + String expected_types = function_name; + if (types.size() == 1) { + expected_types += "\" or \"" + types[0]; + } else if (types.size() >= 2) { + for (int i = 0; i < types.size() - 1; i++) { + expected_types += "\", \"" + types[i]; + } + expected_types += "\", or \"" + types[types.size() - 1]; + } + parser->push_warning(p_call->arguments[0], GDScriptWarning::UNSAFE_CALL_ARGUMENT, "1", "constructor", function_name, expected_types, "Variant"); +#endif + p_call->set_datatype(call_type); + return; } } + List<MethodInfo> constructors; Variant::get_constructor_list(builtin_type, &constructors); bool match = false; @@ -2979,14 +3077,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a for (int i = 0; i < p_call->arguments.size(); i++) { GDScriptParser::DataType par_type = type_from_property(info.arguments[i], true); - - if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype(), true)) { + GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype(); + if (!is_type_compatible(par_type, arg_type, true)) { types_match = false; break; #ifdef DEBUG_ENABLED } else { - if (par_type.builtin_type == Variant::INT && p_call->arguments[i]->get_datatype().builtin_type == Variant::FLOAT && builtin_type != Variant::INT) { - parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); + if (par_type.builtin_type == Variant::INT && arg_type.builtin_type == Variant::FLOAT && builtin_type != Variant::INT) { + parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, function_name); } #endif } @@ -2994,9 +3092,19 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a if (types_match) { for (int i = 0; i < p_call->arguments.size(); i++) { + GDScriptParser::DataType par_type = type_from_property(info.arguments[i], true); if (p_call->arguments[i]->is_constant) { - update_const_expression_builtin_type(p_call->arguments[i], type_from_property(info.arguments[i], true), "pass"); + update_const_expression_builtin_type(p_call->arguments[i], par_type, "pass"); + } +#ifdef DEBUG_ENABLED + if (!(par_type.is_variant() && par_type.is_hard_type())) { + GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype(); + if (arg_type.is_variant() || !arg_type.is_hard_type()) { + mark_node_unsafe(p_call); + parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), "constructor", function_name, par_type.to_string(), arg_type.to_string_strict()); + } } +#endif } match = true; call_type = type_from_property(info.return_val); @@ -3037,12 +3145,16 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a GDScriptUtilityFunctions::get_function(function_name)(&value, (const Variant **)args.ptr(), args.size(), err); switch (err.error) { - case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { - PropertyInfo wrong_arg = function_info.arguments[err.argument]; - push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1, - type_from_property(wrong_arg, true).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()), - p_call->arguments[err.argument]); - } break; + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: + if (value.get_type() == Variant::STRING && !value.operator String().is_empty()) { + push_error(vformat(R"*(Invalid argument for "%s()" function: %s)*", function_name, value), p_call->arguments[err.argument]); + } else { + // Do not use `type_from_property()` for expected type, since utility functions use their own checks. + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1, + Variant::get_type_name((Variant::Type)err.expected), p_call->arguments[err.argument]->get_datatype().to_string()), + p_call->arguments[err.argument]); + } + break; case Callable::CallError::CALL_ERROR_INVALID_METHOD: push_error(vformat(R"(Invalid call for function "%s".)", function_name), p_call); break; @@ -3084,18 +3196,16 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a Variant::call_utility_function(function_name, &value, (const Variant **)args.ptr(), args.size(), err); switch (err.error) { - case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { - String expected_type_name; - if (err.argument < function_info.arguments.size()) { - expected_type_name = type_from_property(function_info.arguments[err.argument], true).to_string(); + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: + if (value.get_type() == Variant::STRING && !value.operator String().is_empty()) { + push_error(vformat(R"*(Invalid argument for "%s()" function: %s)*", function_name, value), p_call->arguments[err.argument]); } else { - expected_type_name = Variant::get_type_name((Variant::Type)err.expected); + // Do not use `type_from_property()` for expected type, since utility functions use their own checks. + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1, + Variant::get_type_name((Variant::Type)err.expected), p_call->arguments[err.argument]->get_datatype().to_string()), + p_call->arguments[err.argument]); } - - push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1, - expected_type_name, p_call->arguments[err.argument]->get_datatype().to_string()), - p_call->arguments[err.argument]); - } break; + break; case Callable::CallError::CALL_ERROR_INVALID_METHOD: push_error(vformat(R"(Invalid call for function "%s".)", function_name), p_call); break; @@ -3185,11 +3295,23 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a bool is_constructor = (base_type.is_meta_type || (p_call->callee && p_call->callee->type == GDScriptParser::Node::IDENTIFIER)) && p_call->function_name == SNAME("new"); + if (is_constructor && Engine::get_singleton()->has_singleton(base_type.native_type)) { + push_error(vformat(R"(Cannot construct native class "%s" because it is an engine singleton.)", base_type.native_type), p_call); + p_call->set_datatype(call_type); + return; + } + if (get_function_signature(p_call, is_constructor, base_type, p_call->function_name, return_type, par_types, default_arg_count, method_flags)) { - // If the function require typed arrays we must make literals be typed. + // If the method is implemented in the class hierarchy, the virtual flag will not be set for that MethodInfo and the search stops there. + // Virtual check only possible for super() calls because class hierarchy is known. Node/Objects may have scripts attached we don't know of at compile-time. + if (p_call->is_super && method_flags.has_flag(METHOD_FLAG_VIRTUAL)) { + push_error(vformat(R"*(Cannot call the parent class' virtual function "%s()" because it hasn't been defined.)*", p_call->function_name), p_call); + } + + // If the function requires typed arrays we must make literals be typed. for (const KeyValue<int, GDScriptParser::ArrayNode *> &E : arrays) { int index = E.key; - if (index < par_types.size() && par_types[index].has_container_element_type()) { + if (index < par_types.size() && par_types[index].is_hard_type() && par_types[index].has_container_element_type()) { update_array_literal_element_type(E.value, par_types[index].get_container_element_type()); } } @@ -3201,15 +3323,16 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } if (is_self && static_context && !method_flags.has_flag(METHOD_FLAG_STATIC)) { - if (parser->current_function) { - // Get the parent function above any lambda. - GDScriptParser::FunctionNode *parent_function = parser->current_function; - while (parent_function->source_lambda) { - parent_function = parent_function->source_lambda->parent_function; - } + // Get the parent function above any lambda. + GDScriptParser::FunctionNode *parent_function = parser->current_function; + while (parent_function && parent_function->source_lambda) { + parent_function = parent_function->source_lambda->parent_function; + } + + if (parent_function) { push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parent_function->identifier->name), p_call); } else { - push_error(vformat(R"*(Cannot call non-static function "%s()" for static variable initializer.)*", p_call->function_name), p_call); + push_error(vformat(R"*(Cannot call non-static function "%s()" from a static variable initializer.)*", p_call->function_name), p_call); } } else if (!is_self && base_type.is_meta_type && !method_flags.has_flag(METHOD_FLAG_STATIC)) { base_type.is_meta_type = false; // For `to_string()`. @@ -3290,8 +3413,8 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a #else push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee); #endif // SUGGEST_GODOT4_RENAMES - } else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::NATIVE && base_type.is_meta_type)) { - push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type), p_call); + } else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.is_meta_type)) { + push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.to_string()), p_call); } } @@ -3439,7 +3562,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base_set_class(GDScriptParser::Ide p_identifier->set_datatype(p_identifier_datatype); Error err = OK; - Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_identifier_datatype.script_path, err); + Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_identifier_datatype.script_path, err, parser->script_path); if (err) { push_error(vformat(R"(Error while getting cache for script "%s".)", p_identifier_datatype.script_path), p_identifier); return; @@ -3779,6 +3902,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident #endif // Not a local, so check members. + if (!found_source) { reduce_identifier_from_base(p_identifier); if (p_identifier->source != GDScriptParser::IdentifierNode::UNDEFINED_SOURCE || p_identifier->get_datatype().is_set()) { @@ -3791,15 +3915,16 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident bool source_is_variable = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE; bool source_is_signal = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_SIGNAL; if ((source_is_variable || source_is_signal) && static_context) { - if (parser->current_function) { - // Get the parent function above any lambda. - GDScriptParser::FunctionNode *parent_function = parser->current_function; - while (parent_function->source_lambda) { - parent_function = parent_function->source_lambda->parent_function; - } + // Get the parent function above any lambda. + GDScriptParser::FunctionNode *parent_function = parser->current_function; + while (parent_function && parent_function->source_lambda) { + parent_function = parent_function->source_lambda->parent_function; + } + + if (parent_function) { push_error(vformat(R"*(Cannot access %s "%s" from the static function "%s()".)*", source_is_signal ? "signal" : "instance variable", p_identifier->name, parent_function->identifier->name), p_identifier); } else { - push_error(vformat(R"*(Cannot access %s "%s" for a static variable initializer.)*", source_is_signal ? "signal" : "instance variable", p_identifier->name), p_identifier); + push_error(vformat(R"*(Cannot access %s "%s" from a static variable initializer.)*", source_is_signal ? "signal" : "instance variable", p_identifier->name), p_identifier); } } @@ -3831,10 +3956,10 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident StringName name = p_identifier->name; p_identifier->source = GDScriptParser::IdentifierNode::UNDEFINED_SOURCE; - // Check globals. We make an exception for Variant::OBJECT because it's the base class for - // non-builtin types so we allow doing e.g. Object.new() + // Not a local or a member, so check globals. + Variant::Type builtin_type = GDScriptParser::get_builtin_type(name); - if (builtin_type != Variant::OBJECT && builtin_type < Variant::VARIANT_MAX) { + if (builtin_type < Variant::VARIANT_MAX) { if (can_be_builtin) { p_identifier->set_datatype(make_builtin_meta_type(builtin_type)); return; @@ -3860,8 +3985,10 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident if (autoload.is_singleton) { // Singleton exists, so it's at least a Node. GDScriptParser::DataType result; - result.kind = GDScriptParser::DataType::NATIVE; result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + result.kind = GDScriptParser::DataType::NATIVE; + result.builtin_type = Variant::OBJECT; + result.native_type = SNAME("Node"); if (ResourceLoader::get_resource_type(autoload.path) == "GDScript") { Ref<GDScriptParserRef> singl_parser = get_parser_for(autoload.path); if (singl_parser.is_valid()) { @@ -4075,7 +4202,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); bool valid = false; // If the base is a metatype, use the analyzer instead. - if (p_subscript->base->is_constant && !base_type.is_meta_type) { + if (p_subscript->base->is_constant && !base_type.is_meta_type && base_type.kind != GDScriptParser::DataType::CLASS) { // Just try to get it. Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid); if (valid) { @@ -4224,7 +4351,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri } } else if (base_type.kind != GDScriptParser::DataType::BUILTIN && !index_type.is_variant()) { if (index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME) { - push_error(vformat(R"(Only String or StringName can be used as index for type "%s", but received a "%s".)", base_type.to_string(), index_type.to_string()), p_subscript->index); + push_error(vformat(R"(Only "String" or "StringName" can be used as index for type "%s", but received "%s".)", base_type.to_string(), index_type.to_string()), p_subscript->index); } } @@ -4563,7 +4690,7 @@ Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::D Ref<Script> script_type = p_element_datatype.script_type; if (p_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) { Error err = OK; - Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_element_datatype.script_path, err); + Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_element_datatype.script_path, err, parser->script_path); if (err) { push_error(vformat(R"(Error while getting cache for script "%s".)", p_element_datatype.script_path), p_source_node); return array; @@ -4734,7 +4861,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo } 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; + elem_type.native_type = elem_type_name; } 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)); @@ -4962,21 +5089,28 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype(); if (arg_type.is_variant() || !arg_type.is_hard_type()) { +#ifdef DEBUG_ENABLED // Argument can be anything, so this is unsafe (unless the parameter is a hard variant). if (!(par_type.is_hard_type() && par_type.is_variant())) { mark_node_unsafe(p_call->arguments[i]); + parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), "function", p_call->function_name, par_type.to_string(), arg_type.to_string_strict()); } +#endif } else if (par_type.is_hard_type() && !is_type_compatible(par_type, arg_type, true)) { - // Supertypes are acceptable for dynamic compliance, but it's unsafe. - mark_node_unsafe(p_call); if (!is_type_compatible(arg_type, par_type)) { push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", p_call->function_name, i + 1, par_type.to_string(), arg_type.to_string()), p_call->arguments[i]); +#ifdef DEBUG_ENABLED + } else { + // Supertypes are acceptable for dynamic compliance, but it's unsafe. + mark_node_unsafe(p_call); + parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), "function", p_call->function_name, par_type.to_string(), arg_type.to_string_strict()); +#endif } #ifdef DEBUG_ENABLED } else if (par_type.kind == GDScriptParser::DataType::BUILTIN && par_type.builtin_type == Variant::INT && arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == Variant::FLOAT) { - parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); + parser->push_warning(p_call->arguments[i], GDScriptWarning::NARROWING_CONVERSION, p_call->function_name); #endif } } @@ -5002,9 +5136,13 @@ void GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_identifier parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in function"); return; } else if (ClassDB::class_exists(name)) { - parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "global class"); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "native class"); + return; + } else if (ScriptServer::is_global_class(name)) { + String class_path = ScriptServer::get_global_class_path(name).get_file(); + parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, vformat(R"(global class defined in "%s")", class_path)); return; - } else if (GDScriptParser::get_builtin_type(name) != Variant::VARIANT_MAX) { + } else if (GDScriptParser::get_builtin_type(name) < Variant::VARIANT_MAX) { parser->push_warning(p_identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_context, name, "built-in type"); return; } @@ -5329,12 +5467,15 @@ void GDScriptAnalyzer::resolve_pending_lambda_bodies() { } GDScriptParser::LambdaNode *previous_lambda = current_lambda; + bool previous_static_context = static_context; List<GDScriptParser::LambdaNode *> lambdas = pending_body_resolution_lambdas; pending_body_resolution_lambdas.clear(); for (GDScriptParser::LambdaNode *lambda : lambdas) { current_lambda = lambda; + static_context = lambda->function->is_static; + resolve_function_body(lambda->function, true); int captures_amount = lambda->captures.size(); @@ -5363,6 +5504,7 @@ void GDScriptAnalyzer::resolve_pending_lambda_bodies() { } current_lambda = previous_lambda; + static_context = previous_static_context; } bool GDScriptAnalyzer::class_exists(const StringName &p_class) const { @@ -5421,7 +5563,7 @@ Error GDScriptAnalyzer::analyze() { // Apply annotations. for (GDScriptParser::AnnotationNode *&E : parser->head->annotations) { resolve_annotation(E); - E->apply(parser, parser->head); + E->apply(parser, parser->head, nullptr); } resolve_interface(); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index af7862efc5..25e20c0e76 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -35,9 +35,6 @@ #include "core/debugger/engine_debugger.h" uint32_t GDScriptByteCodeGenerator::add_parameter(const StringName &p_name, bool p_is_optional, const GDScriptDataType &p_type) { -#ifdef TOOLS_ENABLED - function->arg_names.push_back(p_name); -#endif function->_argument_count++; function->argument_types.push_back(p_type); if (p_is_optional) { @@ -403,7 +400,6 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { } function->_stack_size = RESERVED_STACK + max_locals + temporaries.size(); function->_instruction_args_size = instr_args_max; - function->_ptrcall_args_size = ptrcall_max; #ifdef DEBUG_ENABLED function->operator_names = operator_names; @@ -1228,75 +1224,35 @@ void GDScriptByteCodeGenerator::write_call_method_bind(const Address &p_target, ct.cleanup(); } -void GDScriptByteCodeGenerator::write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) { -#define CASE_TYPE(m_type) \ - case Variant::m_type: \ - append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_PTRCALL_##m_type, 2 + p_arguments.size()); \ - break - - bool is_ptrcall = true; - - if (p_method->has_return()) { - MethodInfo info; - ClassDB::get_method_info(p_method->get_instance_class(), p_method->get_name(), &info); - switch (info.return_val.type) { - CASE_TYPE(BOOL); - CASE_TYPE(INT); - CASE_TYPE(FLOAT); - CASE_TYPE(STRING); - CASE_TYPE(VECTOR2); - CASE_TYPE(VECTOR2I); - CASE_TYPE(RECT2); - CASE_TYPE(RECT2I); - CASE_TYPE(VECTOR3); - CASE_TYPE(VECTOR3I); - CASE_TYPE(TRANSFORM2D); - CASE_TYPE(PLANE); - CASE_TYPE(AABB); - CASE_TYPE(BASIS); - CASE_TYPE(TRANSFORM3D); - CASE_TYPE(COLOR); - CASE_TYPE(STRING_NAME); - CASE_TYPE(NODE_PATH); - CASE_TYPE(RID); - CASE_TYPE(QUATERNION); - CASE_TYPE(OBJECT); - CASE_TYPE(CALLABLE); - CASE_TYPE(SIGNAL); - CASE_TYPE(DICTIONARY); - CASE_TYPE(ARRAY); - CASE_TYPE(PACKED_BYTE_ARRAY); - CASE_TYPE(PACKED_INT32_ARRAY); - CASE_TYPE(PACKED_INT64_ARRAY); - CASE_TYPE(PACKED_FLOAT32_ARRAY); - CASE_TYPE(PACKED_FLOAT64_ARRAY); - CASE_TYPE(PACKED_STRING_ARRAY); - CASE_TYPE(PACKED_VECTOR2_ARRAY); - CASE_TYPE(PACKED_VECTOR3_ARRAY); - CASE_TYPE(PACKED_COLOR_ARRAY); - default: - append_opcode_and_argcount(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL_METHOD_BIND : GDScriptFunction::OPCODE_CALL_METHOD_BIND_RET, 2 + p_arguments.size()); - is_ptrcall = false; - break; +void GDScriptByteCodeGenerator::write_call_method_bind_validated(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) { + Variant::Type return_type = Variant::NIL; + bool has_return = p_method->has_return(); + + if (has_return) { + PropertyInfo return_info = p_method->get_return_info(); + return_type = return_info.type; + } + + CallTarget ct = get_call_target(p_target, return_type); + + if (has_return) { + Variant::Type temp_type = temporaries[ct.target.address].type; + if (temp_type != return_type) { + write_type_adjust(ct.target, return_type); } - } else { - append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_PTRCALL_NO_RETURN, 2 + p_arguments.size()); } + GDScriptFunction::Opcode code = p_method->has_return() ? GDScriptFunction::OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN : GDScriptFunction::OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN; + append_opcode_and_argcount(code, 2 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } append(p_base); - CallTarget ct = get_call_target(p_target); append(ct.target); append(p_arguments.size()); append(p_method); ct.cleanup(); - if (is_ptrcall) { - alloc_ptrcall(p_arguments.size()); - } - -#undef CASE_TYPE } void GDScriptByteCodeGenerator::write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) { diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 671dea5d6d..9bface6136 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -97,7 +97,6 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { int max_locals = 0; int current_line = 0; int instr_args_max = 0; - int ptrcall_max = 0; #ifdef DEBUG_ENABLED List<int> temp_stack; @@ -346,12 +345,6 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { return pos; } - void alloc_ptrcall(int p_params) { - if (p_params >= ptrcall_max) { - ptrcall_max = p_params; - } - } - CallTarget get_call_target(const Address &p_target, Variant::Type p_type = Variant::NIL); int address_of(const Address &p_address) { @@ -519,7 +512,7 @@ public: 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_native_static(const Address &p_target, const StringName &p_class, 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_method_bind_validated(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; diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index d191bd0224..76f4e69ab9 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -59,7 +59,7 @@ GDScriptAnalyzer *GDScriptParserRef::get_analyzer() { } Error GDScriptParserRef::raise_status(Status p_new_status) { - ERR_FAIL_COND_V(parser == nullptr, ERR_INVALID_DATA); + ERR_FAIL_NULL_V(parser, ERR_INVALID_DATA); if (result != OK) { return result; @@ -287,7 +287,8 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro if (script.is_null()) { script = get_shallow_script(p_path, r_error); - if (r_error) { + // Only exit early if script failed to load, otherwise let reload report errors. + if (script.is_null()) { return script; } } @@ -363,28 +364,33 @@ void GDScriptCache::remove_static_script(const String &p_fqcn) { Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_error, const String &p_owner) { MutexLock lock(singleton->mutex); - if (singleton->packed_scene_cache.has(p_path)) { - singleton->packed_scene_dependencies[p_path].insert(p_owner); - return singleton->packed_scene_cache[p_path]; + String path = p_path; + if (path.begins_with("uid://")) { + path = ResourceUID::get_singleton()->get_id_path(ResourceUID::get_singleton()->text_to_id(path)); + } + + if (singleton->packed_scene_cache.has(path)) { + singleton->packed_scene_dependencies[path].insert(p_owner); + return singleton->packed_scene_cache[path]; } - Ref<PackedScene> scene = ResourceCache::get_ref(p_path); + Ref<PackedScene> scene = ResourceCache::get_ref(path); if (scene.is_valid()) { - singleton->packed_scene_cache[p_path] = scene; - singleton->packed_scene_dependencies[p_path].insert(p_owner); + singleton->packed_scene_cache[path] = scene; + singleton->packed_scene_dependencies[path].insert(p_owner); return scene; } scene.instantiate(); r_error = OK; - if (p_path.is_empty()) { + if (path.is_empty()) { r_error = ERR_FILE_BAD_PATH; return scene; } - scene->set_path(p_path); - singleton->packed_scene_cache[p_path] = scene; - singleton->packed_scene_dependencies[p_path].insert(p_owner); + scene->set_path(path); + singleton->packed_scene_cache[path] = scene; + singleton->packed_scene_dependencies[path].insert(p_owner); scene->reload_from_file(); return scene; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index cf17353dec..7ad8f841aa 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -129,7 +129,7 @@ public: 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_native_static(const Address &p_target, const StringName &p_class, 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_method_bind_validated(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; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 985eb97b29..7980f020b8 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -60,7 +60,7 @@ bool GDScriptCompiler::_is_class_member_property(GDScript *owner, const StringNa scr = scr->_base; } - ERR_FAIL_COND_V(!nc, false); + ERR_FAIL_NULL_V(nc, false); return ClassDB::has_property(nc->get_name(), p_name); } @@ -84,7 +84,7 @@ void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::N } } -GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) { +GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner, bool p_handle_metatype) { if (!p_datatype.is_set() || !p_datatype.is_hard_type() || p_datatype.is_coroutine) { return GDScriptDataType(); } @@ -101,11 +101,36 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D result.builtin_type = p_datatype.builtin_type; } break; case GDScriptParser::DataType::NATIVE: { + if (p_handle_metatype && p_datatype.is_meta_type) { + result.kind = GDScriptDataType::NATIVE; + result.builtin_type = Variant::OBJECT; + // Fixes GH-82255. `GDScriptNativeClass` is obtainable in GDScript, + // but is not a registered and exposed class, so `GDScriptNativeClass` + // is missing from `GDScriptLanguage::get_singleton()->get_global_map()`. + //result.native_type = GDScriptNativeClass::get_class_static(); + result.native_type = Object::get_class_static(); + break; + } + result.kind = GDScriptDataType::NATIVE; - result.native_type = p_datatype.native_type; result.builtin_type = p_datatype.builtin_type; + result.native_type = p_datatype.native_type; + +#ifdef DEBUG_ENABLED + if (unlikely(!GDScriptLanguage::get_singleton()->get_global_map().has(result.native_type))) { + ERR_PRINT(vformat(R"(GDScript bug: Native class "%s" not found.)", result.native_type)); + result.native_type = Object::get_class_static(); + } +#endif } break; case GDScriptParser::DataType::SCRIPT: { + if (p_handle_metatype && p_datatype.is_meta_type) { + result.kind = GDScriptDataType::NATIVE; + result.builtin_type = Variant::OBJECT; + result.native_type = p_datatype.script_type.is_valid() ? p_datatype.script_type->get_class() : Script::get_class_static(); + break; + } + result.kind = GDScriptDataType::SCRIPT; result.builtin_type = p_datatype.builtin_type; result.script_type_ref = p_datatype.script_type; @@ -113,6 +138,13 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D result.native_type = p_datatype.native_type; } break; case GDScriptParser::DataType::CLASS: { + if (p_handle_metatype && p_datatype.is_meta_type) { + result.kind = GDScriptDataType::NATIVE; + result.builtin_type = Variant::OBJECT; + result.native_type = GDScript::get_class_static(); + break; + } + result.kind = GDScriptDataType::GDSCRIPT; result.builtin_type = p_datatype.builtin_type; result.native_type = p_datatype.native_type; @@ -148,6 +180,12 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } } break; case GDScriptParser::DataType::ENUM: + if (p_handle_metatype && p_datatype.is_meta_type) { + result.kind = GDScriptDataType::BUILTIN; + result.builtin_type = Variant::DICTIONARY; + break; + } + result.kind = GDScriptDataType::BUILTIN; result.builtin_type = p_datatype.builtin_type; break; @@ -159,7 +197,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } if (p_datatype.has_container_element_type()) { - result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type(), p_owner)); + result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type(), p_owner, false)); } return result; @@ -191,13 +229,13 @@ static bool _is_exact_type(const PropertyInfo &p_par_type, const GDScriptDataTyp } } -static bool _can_use_ptrcall(const MethodBind *p_method, const Vector<GDScriptCodeGenerator::Address> &p_arguments) { +static bool _can_use_validate_call(const MethodBind *p_method, const Vector<GDScriptCodeGenerator::Address> &p_arguments) { if (p_method->is_vararg()) { - // ptrcall won't work with vararg methods. + // Validated call won't work with vararg methods. return false; } if (p_method->get_argument_count() != p_arguments.size()) { - // ptrcall won't work with default arguments. + // Validated call won't work with default arguments. return false; } MethodInfo info; @@ -402,7 +440,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code String global_class_path = ScriptServer::get_global_class_path(identifier); if (ResourceLoader::get_resource_type(global_class_path) == "GDScript") { Error err = OK; - res = GDScriptCache::get_full_script(global_class_path, err); + // Should not need to pass p_owner since analyzer will already have done it. + res = GDScriptCache::get_shallow_script(global_class_path, err); if (err != OK) { _set_error("Can't load global class " + String(identifier), p_expression); r_error = ERR_COMPILATION_FAILED; @@ -533,7 +572,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } break; case GDScriptParser::Node::CAST: { const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); - GDScriptDataType cast_type = _gdtype_from_datatype(cn->get_datatype(), codegen.script); + GDScriptDataType cast_type = _gdtype_from_datatype(cn->get_datatype(), codegen.script, false); GDScriptCodeGenerator::Address result; if (cast_type.has_type) { @@ -573,11 +612,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code arguments.push_back(arg); } - if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(call->function_name) != Variant::VARIANT_MAX) { - // Construct a built-in type. - Variant::Type vtype = GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(call->callee)->name); - - gen->write_construct(result, vtype, arguments); + if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(call->function_name) < Variant::VARIANT_MAX) { + gen->write_construct(result, GDScriptParser::get_builtin_type(call->function_name), arguments); } else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && Variant::has_utility_function(call->function_name)) { // Variant utility function. gen->write_call_utility(result, call->function_name, arguments); @@ -600,9 +636,9 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code self.mode = GDScriptCodeGenerator::Address::SELF; MethodBind *method = ClassDB::get_method(codegen.script->native->get_name(), call->function_name); - if (_can_use_ptrcall(method, arguments)) { - // Exact arguments, use ptrcall. - gen->write_call_ptrcall(result, self, method, arguments); + if (_can_use_validate_call(method, arguments)) { + // Exact arguments, use validated call. + gen->write_call_method_bind_validated(result, self, method, arguments); } else { // Not exact arguments, but still can use method bind call. gen->write_call_method_bind(result, self, method, arguments); @@ -650,9 +686,9 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } 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 (_can_use_ptrcall(method, arguments)) { - // Exact arguments, use ptrcall. - gen->write_call_ptrcall(result, base, method, arguments); + if (_can_use_validate_call(method, arguments)) { + // Exact arguments, use validated call. + gen->write_call_method_bind_validated(result, base, method, arguments); } else { // Not exact arguments, but still can use method bind call. gen->write_call_method_bind(result, base, method, arguments); @@ -697,7 +733,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype(), codegen.script)); MethodBind *get_node_method = ClassDB::get_method("Node", "get_node"); - gen->write_call_ptrcall(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args); + gen->write_call_method_bind_validated(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args); return result; } break; @@ -911,7 +947,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(type_test->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, type_test->operand); - GDScriptDataType test_type = _gdtype_from_datatype(type_test->test_datatype, codegen.script); + GDScriptDataType test_type = _gdtype_from_datatype(type_test->test_datatype, codegen.script, false); if (r_error) { return GDScriptCodeGenerator::Address(); } @@ -1335,6 +1371,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code return GDScriptCodeGenerator::Address(); } + main_script->lambda_info.insert(function, { lambda->captures.size(), lambda->use_self }); gen->write_lambda(result, function, captures, lambda->use_self); for (int i = 0; i < captures.size(); i++) { @@ -1889,6 +1926,26 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui } } + // If there's a guard, check its condition too. + if (branch->guard_body != nullptr) { + // Do this first so the guard does not run unless the pattern matched. + gen->write_and_left_operand(pattern_result); + + // Don't actually use the block for the guard. + // The binds are already in the locals and we don't want to clear the result of the guard condition before we check the actual match. + GDScriptCodeGenerator::Address guard_result = _parse_expression(codegen, err, static_cast<GDScriptParser::ExpressionNode *>(branch->guard_body->statements[0])); + if (err) { + return err; + } + + gen->write_and_right_operand(guard_result); + gen->write_end_and(pattern_result); + + if (guard_result.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + } + // Check if pattern did match. gen->write_if(pattern_result); @@ -2165,8 +2222,14 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ } } + MethodInfo method_info; + codegen.function_name = func_name; + method_info.name = func_name; codegen.is_static = is_static; + if (is_static) { + method_info.flags |= METHOD_FLAG_STATIC; + } codegen.generator->write_start(p_script, func_name, is_static, rpc_config, return_type); int optional_parameters = 0; @@ -2178,10 +2241,14 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ uint32_t par_addr = codegen.generator->add_parameter(parameter->identifier->name, parameter->initializer != nullptr, par_type); codegen.parameters[parameter->identifier->name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::FUNCTION_PARAMETER, par_addr, par_type); + method_info.arguments.push_back(parameter->get_datatype().to_property_info(parameter->identifier->name)); + if (parameter->initializer != nullptr) { optional_parameters++; } } + + method_info.default_arguments.append_array(p_func->default_arg_values); } // Parse initializer if applies. @@ -2335,20 +2402,20 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ } if (p_func) { - // if no return statement -> return type is void not unresolved Variant + // If no `return` statement, then return type is `void`, not `Variant`. if (p_func->body->has_return) { gd_function->return_type = _gdtype_from_datatype(p_func->get_datatype(), p_script); + method_info.return_val = p_func->get_datatype().to_property_info(String()); } else { gd_function->return_type = GDScriptDataType(); gd_function->return_type.has_type = true; gd_function->return_type.kind = GDScriptDataType::BUILTIN; gd_function->return_type.builtin_type = Variant::NIL; } -#ifdef TOOLS_ENABLED - gd_function->default_arg_values = p_func->default_arg_values; -#endif } + gd_function->method_info = method_info; + if (!is_implicit_initializer && !is_implicit_ready && !p_for_lambda) { p_script->member_functions[func_name] = gd_function; } @@ -2503,7 +2570,10 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP return err; } -Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { +// Prepares given script, and inner class scripts, for compilation. It populates class members and initializes method +// RPC info for its base classes first, then for itself, then for inner classes. +// Warning: this function cannot initiate compilation of other classes, or it will result in cyclic dependency issues. +Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { if (parsed_classes.has(p_script)) { return OK; } @@ -2554,7 +2624,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri p_script->member_functions.clear(); p_script->member_indices.clear(); - p_script->member_info.clear(); p_script->static_variables_indices.clear(); p_script->static_variables.clear(); p_script->_signals.clear(); @@ -2562,19 +2631,21 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri p_script->implicit_initializer = nullptr; p_script->implicit_ready = nullptr; p_script->static_initializer = nullptr; + p_script->rpc_config.clear(); + p_script->lambda_info.clear(); p_script->clearing = false; p_script->tool = parser->is_tool(); - if (!p_script->name.is_empty()) { - if (ClassDB::class_exists(p_script->name) && ClassDB::is_class_exposed(p_script->name)) { - _set_error("The class '" + p_script->name + "' shadows a native class", p_class); + if (p_script->local_name != StringName()) { + if (ClassDB::class_exists(p_script->local_name) && ClassDB::is_class_exposed(p_script->local_name)) { + _set_error(vformat(R"(The class "%s" shadows a native class)", p_script->local_name), p_class); return ERR_ALREADY_EXISTS; } } - GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type, p_script); + GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type, p_script, false); int native_idx = GDScriptLanguage::get_singleton()->get_global_map()[base_type.native_type]; p_script->native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx]; @@ -2592,7 +2663,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri } if (main_script->has_class(base.ptr())) { - Error err = _populate_class_members(base.ptr(), p_class->base_type.class_type, p_keep_state); + Error err = _prepare_compilation(base.ptr(), p_class->base_type.class_type, p_keep_state); if (err) { return err; } @@ -2611,7 +2682,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri return ERR_COMPILATION_FAILED; } - err = _populate_class_members(base.ptr(), p_class->base_type.class_type, p_keep_state); + err = _prepare_compilation(base.ptr(), p_class->base_type.class_type, p_keep_state); if (err) { _set_error(vformat(R"(Could not populate class members of base class "%s" in "%s".)", base->fully_qualified_name, base->path), nullptr); return err; @@ -2628,6 +2699,12 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri } break; } + // Duplicate RPC information from base GDScript + // Base script isn't valid because it should not have been compiled yet, but the reference contains relevant info. + if (base_type.kind == GDScriptDataType::GDSCRIPT && p_script->base.is_valid()) { + p_script->rpc_config = p_script->base->rpc_config.duplicate(); + } + for (int i = 0; i < p_class->members.size(); i++) { const GDScriptParser::ClassNode::Member &member = p_class->members[i]; switch (member.type) { @@ -2636,7 +2713,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri StringName name = variable->identifier->name; GDScript::MemberInfo minfo; - minfo.index = p_script->member_indices.size(); switch (variable->property) { case GDScriptParser::VariableNode::PROP_NONE: break; // Nothing to do. @@ -2659,8 +2735,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri } minfo.data_type = _gdtype_from_datatype(variable->get_datatype(), p_script); - PropertyInfo prop_info = minfo.data_type; - prop_info.name = name; + PropertyInfo prop_info = variable->get_datatype().to_property_info(name); PropertyInfo export_info = variable->export_info; if (variable->exported) { @@ -2670,16 +2745,16 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri } prop_info.hint = export_info.hint; prop_info.hint_string = export_info.hint_string; - prop_info.usage = export_info.usage | PROPERTY_USAGE_SCRIPT_VARIABLE; - } else { - prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE; + prop_info.usage = export_info.usage; } + prop_info.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; + minfo.property_info = prop_info; if (variable->is_static) { minfo.index = p_script->static_variables_indices.size(); p_script->static_variables_indices[name] = minfo; } else { - p_script->member_info[name] = prop_info; + minfo.index = p_script->member_indices.size(); p_script->member_indices[name] = minfo; p_script->members.insert(name); } @@ -2712,12 +2787,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri const GDScriptParser::SignalNode *signal = member.signal; StringName name = signal->identifier->name; - Vector<StringName> parameters_names; - parameters_names.resize(signal->parameters.size()); - for (int j = 0; j < signal->parameters.size(); j++) { - parameters_names.write[j] = signal->parameters[j]->identifier->name; - } - p_script->_signals[name] = parameters_names; + p_script->_signals[name] = signal->method_info; } break; case GDScriptParser::ClassNode::Member::ENUM: { @@ -2740,12 +2810,20 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri prop_info.name = annotation->export_info.name; prop_info.usage = annotation->export_info.usage; prop_info.hint_string = annotation->export_info.hint_string; + minfo.property_info = prop_info; - p_script->member_info[name] = prop_info; p_script->member_indices[name] = minfo; - p_script->members.insert(Variant()); + p_script->members.insert(name); } break; + case GDScriptParser::ClassNode::Member::FUNCTION: { + const GDScriptParser::FunctionNode *function_n = member.function; + + Variant config = function_n->rpc_config; + if (config.get_type() != Variant::NIL) { + p_script->rpc_config[function_n->identifier->name] = config; + } + } break; default: break; // Nothing to do here. } @@ -2756,7 +2834,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri parsed_classes.insert(p_script); parsing_classes.erase(p_script); - // Populate sub-classes. + // Populate inner classes. for (int i = 0; i < p_class->members.size(); i++) { const GDScriptParser::ClassNode::Member &member = p_class->members[i]; if (member.type != member.CLASS) { @@ -2769,7 +2847,7 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri // Subclass might still be parsing, just skip it if (!parsing_classes.has(subclass_ptr)) { - Error err = _populate_class_members(subclass_ptr, inner_class, p_keep_state); + Error err = _prepare_compilation(subclass_ptr, inner_class, p_keep_state); if (err) { return err; } @@ -2905,8 +2983,6 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser: has_static_data = has_static_data || inner_class->has_static_data; } - p_script->_init_rpc_methods_properties(); - p_script->valid = true; return OK; } @@ -2927,7 +3003,8 @@ void GDScriptCompiler::convert_to_initializer_type(Variant &p_variant, const GDS void GDScriptCompiler::make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { p_script->fully_qualified_name = p_class->fqcn; - p_script->name = p_class->identifier ? p_class->identifier->name : ""; + p_script->local_name = p_class->identifier ? p_class->identifier->name : StringName(); + p_script->global_name = p_class->get_global_name(); p_script->simplified_icon_path = p_class->simplified_icon_path; HashMap<StringName, Ref<GDScript>> old_subclasses; @@ -2965,6 +3042,128 @@ void GDScriptCompiler::make_scripts(GDScript *p_script, const GDScriptParser::Cl } } +GDScriptCompiler::FunctionLambdaInfo GDScriptCompiler::_get_function_replacement_info(GDScriptFunction *p_func, int p_index, int p_depth, GDScriptFunction *p_parent_func) { + FunctionLambdaInfo info; + info.function = p_func; + info.parent = p_parent_func; + info.script = p_parent_func; + info.name = p_func->get_name(); + info.line = p_func->_initial_line; + info.index = p_index; + info.depth = p_depth; + info.capture_count = 0; + info.use_self = false; + info.arg_count = p_func->_argument_count; + info.default_arg_count = p_func->_default_arg_count; + info.sublambdas = _get_function_lambda_replacement_info(p_func, p_depth, p_parent_func); + + GDScript::LambdaInfo *extra_info = main_script->lambda_info.getptr(p_func); + if (extra_info != nullptr) { + info.capture_count = extra_info->capture_count; + info.use_self = extra_info->use_self; + } + + return info; +} + +Vector<GDScriptCompiler::FunctionLambdaInfo> GDScriptCompiler::_get_function_lambda_replacement_info(GDScriptFunction *p_func, int p_depth, GDScriptFunction *p_parent_func) { + Vector<FunctionLambdaInfo> result; + // Only scrape the lambdas inside p_func. + for (int i = 0; i < p_func->lambdas.size(); ++i) { + result.push_back(_get_function_replacement_info(p_func->lambdas[i], i, p_depth + 1, p_func)); + } + return result; +} + +GDScriptCompiler::ScriptLambdaInfo GDScriptCompiler::_get_script_lambda_replacement_info(GDScript *p_script) { + ScriptLambdaInfo info; + + if (p_script->implicit_initializer) { + info.implicit_initializer_info = _get_function_lambda_replacement_info(p_script->implicit_initializer); + } + if (p_script->implicit_ready) { + info.implicit_ready_info = _get_function_lambda_replacement_info(p_script->implicit_ready); + } + if (p_script->static_initializer) { + info.static_initializer_info = _get_function_lambda_replacement_info(p_script->static_initializer); + } + + for (const KeyValue<StringName, GDScriptFunction *> &E : p_script->member_functions) { + info.member_function_infos.insert(E.key, _get_function_lambda_replacement_info(E.value)); + } + + for (const KeyValue<StringName, Ref<GDScript>> &KV : p_script->get_subclasses()) { + info.subclass_info.insert(KV.key, _get_script_lambda_replacement_info(KV.value.ptr())); + } + + return info; +} + +bool GDScriptCompiler::_do_function_infos_match(const FunctionLambdaInfo &p_old_info, const FunctionLambdaInfo *p_new_info) { + if (p_new_info == nullptr) { + return false; + } + + if (p_new_info->capture_count != p_old_info.capture_count || p_new_info->use_self != p_old_info.use_self) { + return false; + } + + int old_required_arg_count = p_old_info.arg_count - p_old_info.default_arg_count; + int new_required_arg_count = p_new_info->arg_count - p_new_info->default_arg_count; + if (new_required_arg_count > old_required_arg_count || p_new_info->arg_count < old_required_arg_count) { + return false; + } + + return true; +} + +void GDScriptCompiler::_get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const FunctionLambdaInfo &p_old_info, const FunctionLambdaInfo *p_new_info) { + ERR_FAIL_COND(r_replacements.has(p_old_info.function)); + if (!_do_function_infos_match(p_old_info, p_new_info)) { + p_new_info = nullptr; + } + + r_replacements.insert(p_old_info.function, p_new_info != nullptr ? p_new_info->function : nullptr); + _get_function_ptr_replacements(r_replacements, p_old_info.sublambdas, p_new_info != nullptr ? &p_new_info->sublambdas : nullptr); +} + +void GDScriptCompiler::_get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const Vector<FunctionLambdaInfo> &p_old_infos, const Vector<FunctionLambdaInfo> *p_new_infos) { + for (int i = 0; i < p_old_infos.size(); ++i) { + const FunctionLambdaInfo &old_info = p_old_infos[i]; + const FunctionLambdaInfo *new_info = nullptr; + if (p_new_infos != nullptr && p_new_infos->size() == p_old_infos.size()) { + // For now only attempt if the size is the same. + new_info = &p_new_infos->get(i); + } + _get_function_ptr_replacements(r_replacements, old_info, new_info); + } +} + +void GDScriptCompiler::_get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const ScriptLambdaInfo &p_old_info, const ScriptLambdaInfo *p_new_info) { + _get_function_ptr_replacements(r_replacements, p_old_info.implicit_initializer_info, p_new_info != nullptr ? &p_new_info->implicit_initializer_info : nullptr); + _get_function_ptr_replacements(r_replacements, p_old_info.implicit_ready_info, p_new_info != nullptr ? &p_new_info->implicit_ready_info : nullptr); + _get_function_ptr_replacements(r_replacements, p_old_info.static_initializer_info, p_new_info != nullptr ? &p_new_info->static_initializer_info : nullptr); + + for (const KeyValue<StringName, Vector<FunctionLambdaInfo>> &old_kv : p_old_info.member_function_infos) { + _get_function_ptr_replacements(r_replacements, old_kv.value, p_new_info != nullptr ? p_new_info->member_function_infos.getptr(old_kv.key) : nullptr); + } + for (int i = 0; i < p_old_info.other_function_infos.size(); ++i) { + const FunctionLambdaInfo &old_other_info = p_old_info.other_function_infos[i]; + const FunctionLambdaInfo *new_other_info = nullptr; + if (p_new_info != nullptr && p_new_info->other_function_infos.size() == p_old_info.other_function_infos.size()) { + // For now only attempt if the size is the same. + new_other_info = &p_new_info->other_function_infos[i]; + } + // Needs to be called on all old lambdas, even if there's no replacement. + _get_function_ptr_replacements(r_replacements, old_other_info, new_other_info); + } + for (const KeyValue<StringName, ScriptLambdaInfo> &old_kv : p_old_info.subclass_info) { + const ScriptLambdaInfo &old_subinfo = old_kv.value; + const ScriptLambdaInfo *new_subinfo = p_new_info != nullptr ? p_new_info->subclass_info.getptr(old_kv.key) : nullptr; + _get_function_ptr_replacements(r_replacements, old_subinfo, new_subinfo); + } +} + Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state) { err_line = -1; err_column = -1; @@ -2975,11 +3174,13 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri source = p_script->get_path(); + ScriptLambdaInfo old_lambda_info = _get_script_lambda_replacement_info(p_script); + // Create scripts for subclasses beforehand so they can be referenced make_scripts(p_script, root, p_keep_state); main_script->_owner = nullptr; - Error err = _populate_class_members(main_script, parser->get_tree(), p_keep_state); + Error err = _prepare_compilation(main_script, parser->get_tree(), p_keep_state); if (err) { return err; @@ -2990,6 +3191,27 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri return err; } + ScriptLambdaInfo new_lambda_info = _get_script_lambda_replacement_info(p_script); + + HashMap<GDScriptFunction *, GDScriptFunction *> func_ptr_replacements; + _get_function_ptr_replacements(func_ptr_replacements, old_lambda_info, &new_lambda_info); + + { + MutexLock outer_lock(main_script->func_ptrs_to_update_mutex); + for (GDScript::UpdatableFuncPtr *updatable : main_script->func_ptrs_to_update) { + MutexLock inner_lock(updatable->mutex); + for (GDScriptFunction **func_ptr_ptr : updatable->ptrs) { + GDScriptFunction **replacement = func_ptr_replacements.getptr(*func_ptr_ptr); + if (replacement != nullptr) { + *func_ptr_ptr = *replacement; + } else { + // Probably a lambda from another reload, ignore. + *func_ptr_ptr = nullptr; + } + } + } + } + if (has_static_data && !root->annotated_static_unload) { GDScriptCache::add_static_script(p_script); } diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 2f522da4ea..fd6b22f527 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -44,6 +44,34 @@ class GDScriptCompiler { HashSet<GDScript *> parsing_classes; GDScript *main_script = nullptr; + struct FunctionLambdaInfo { + GDScriptFunction *function; + GDScriptFunction *parent; + Ref<GDScript> script; + StringName name; + int line; + int index; + int depth; + //uint64_t code_hash; + //int code_size; + int capture_count; + int use_self; + int arg_count; + int default_arg_count; + //Vector<GDScriptDataType> argument_types; + //GDScriptDataType return_type; + Vector<FunctionLambdaInfo> sublambdas; + }; + + struct ScriptLambdaInfo { + Vector<FunctionLambdaInfo> implicit_initializer_info; + Vector<FunctionLambdaInfo> implicit_ready_info; + Vector<FunctionLambdaInfo> static_initializer_info; + HashMap<StringName, Vector<FunctionLambdaInfo>> member_function_infos; + Vector<FunctionLambdaInfo> other_function_infos; + HashMap<StringName, ScriptLambdaInfo> subclass_info; + }; + struct CodeGen { GDScript *script = nullptr; const GDScriptParser::ClassNode *class_node = nullptr; @@ -124,7 +152,7 @@ class GDScriptCompiler { Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); - GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner); + GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner, bool p_handle_metatype = true); GDScriptCodeGenerator::Address _parse_assign_right_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::AssignmentNode *p_assignmentint, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); @@ -135,8 +163,15 @@ class GDScriptCompiler { 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); GDScriptFunction *_make_static_initializer(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class); Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter); - Error _populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + Error _prepare_compilation(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); Error _compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + FunctionLambdaInfo _get_function_replacement_info(GDScriptFunction *p_func, int p_index = -1, int p_depth = 0, GDScriptFunction *p_parent_func = nullptr); + Vector<FunctionLambdaInfo> _get_function_lambda_replacement_info(GDScriptFunction *p_func, int p_depth = 0, GDScriptFunction *p_parent_func = nullptr); + ScriptLambdaInfo _get_script_lambda_replacement_info(GDScript *p_script); + bool _do_function_infos_match(const FunctionLambdaInfo &p_old_info, const FunctionLambdaInfo *p_new_info); + void _get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const FunctionLambdaInfo &p_old_info, const FunctionLambdaInfo *p_new_info); + void _get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const Vector<FunctionLambdaInfo> &p_old_infos, const Vector<FunctionLambdaInfo> *p_new_infos); + void _get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const ScriptLambdaInfo &p_old_info, const ScriptLambdaInfo *p_new_info); int err_line = 0; int err_column = 0; StringName source; diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index 438ec02740..26f7cb7537 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -670,10 +670,29 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 4 + argc; } break; - case OPCODE_CALL_PTRCALL_NO_RETURN: { + + case OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN: { int instr_var_args = _code_ptr[++ip]; + text += "call method-bind validated (return) "; + MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]]; + int argc = _code_ptr[ip + 1 + instr_var_args]; + text += DADDR(2 + argc) + " = "; + text += DADDR(1 + argc) + "."; + text += method->get_name(); + text += "("; + for (int i = 0; i < argc; i++) { + if (i > 0) + text += ", "; + text += DADDR(1 + i); + } + text += ")"; + incr = 5 + argc; + } break; - text += "call-ptrcall (no return) "; + case OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN: { + int instr_var_args = _code_ptr[++ip]; + + text += "call method-bind validated (no return) "; MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]]; @@ -694,65 +713,6 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr = 5 + argc; } break; -#define DISASSEMBLE_PTRCALL(m_type) \ - case OPCODE_CALL_PTRCALL_##m_type: { \ - int instr_var_args = _code_ptr[++ip]; \ - text += "call-ptrcall (return "; \ - text += #m_type; \ - text += ") "; \ - MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]]; \ - int argc = _code_ptr[ip + 1 + instr_var_args]; \ - text += DADDR(2 + argc) + " = "; \ - text += DADDR(1 + argc) + "."; \ - text += method->get_name(); \ - text += "("; \ - for (int i = 0; i < argc; i++) { \ - if (i > 0) \ - text += ", "; \ - text += DADDR(1 + i); \ - } \ - text += ")"; \ - incr = 5 + argc; \ - } break - - DISASSEMBLE_PTRCALL(BOOL); - DISASSEMBLE_PTRCALL(INT); - DISASSEMBLE_PTRCALL(FLOAT); - DISASSEMBLE_PTRCALL(STRING); - DISASSEMBLE_PTRCALL(VECTOR2); - DISASSEMBLE_PTRCALL(VECTOR2I); - DISASSEMBLE_PTRCALL(RECT2); - DISASSEMBLE_PTRCALL(RECT2I); - DISASSEMBLE_PTRCALL(VECTOR3); - DISASSEMBLE_PTRCALL(VECTOR3I); - DISASSEMBLE_PTRCALL(TRANSFORM2D); - DISASSEMBLE_PTRCALL(VECTOR4); - DISASSEMBLE_PTRCALL(VECTOR4I); - DISASSEMBLE_PTRCALL(PLANE); - DISASSEMBLE_PTRCALL(AABB); - DISASSEMBLE_PTRCALL(BASIS); - DISASSEMBLE_PTRCALL(TRANSFORM3D); - DISASSEMBLE_PTRCALL(PROJECTION); - DISASSEMBLE_PTRCALL(COLOR); - DISASSEMBLE_PTRCALL(STRING_NAME); - DISASSEMBLE_PTRCALL(NODE_PATH); - DISASSEMBLE_PTRCALL(RID); - DISASSEMBLE_PTRCALL(QUATERNION); - DISASSEMBLE_PTRCALL(OBJECT); - DISASSEMBLE_PTRCALL(CALLABLE); - DISASSEMBLE_PTRCALL(SIGNAL); - DISASSEMBLE_PTRCALL(DICTIONARY); - DISASSEMBLE_PTRCALL(ARRAY); - DISASSEMBLE_PTRCALL(PACKED_BYTE_ARRAY); - DISASSEMBLE_PTRCALL(PACKED_INT32_ARRAY); - DISASSEMBLE_PTRCALL(PACKED_INT64_ARRAY); - DISASSEMBLE_PTRCALL(PACKED_FLOAT32_ARRAY); - DISASSEMBLE_PTRCALL(PACKED_FLOAT64_ARRAY); - DISASSEMBLE_PTRCALL(PACKED_STRING_ARRAY); - DISASSEMBLE_PTRCALL(PACKED_VECTOR2_ARRAY); - DISASSEMBLE_PTRCALL(PACKED_VECTOR3_ARRAY); - DISASSEMBLE_PTRCALL(PACKED_COLOR_ARRAY); - case OPCODE_CALL_BUILTIN_TYPE_VALIDATED: { int instr_var_args = _code_ptr[++ip]; int argc = _code_ptr[ip + 1 + instr_var_args]; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 6cad3b2b90..724715d9e5 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -54,11 +54,16 @@ void GDScriptLanguage::get_comment_delimiters(List<String> *p_delimiters) const p_delimiters->push_back("#"); } +void GDScriptLanguage::get_doc_comment_delimiters(List<String> *p_delimiters) const { + p_delimiters->push_back("##"); +} + void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const { p_delimiters->push_back("\" \""); p_delimiters->push_back("' '"); p_delimiters->push_back("\"\"\" \"\"\""); p_delimiters->push_back("''' '''"); + // NOTE: StringName, NodePath and r-strings are not listed here. } bool GDScriptLanguage::is_using_templates() { @@ -75,19 +80,25 @@ Ref<Script> GDScriptLanguage::make_template(const String &p_template, const Stri #endif if (!type_hints) { processed_template = processed_template.replace(": int", "") + .replace(": Shader.Mode", "") + .replace(": VisualShader.Type", "") + .replace(": float", "") .replace(": String", "") .replace(": Array[String]", "") - .replace(": float", "") + .replace(": Node", "") .replace(": CharFXTransform", "") .replace(":=", "=") - .replace(" -> String", "") - .replace(" -> int", "") + .replace(" -> void", "") .replace(" -> bool", "") - .replace(" -> void", ""); + .replace(" -> int", "") + .replace(" -> PortType", "") + .replace(" -> String", "") + .replace(" -> Object", ""); } processed_template = processed_template.replace("_BASE_", p_base_class_name) - .replace("_CLASS_", p_class_name.to_pascal_case()) + .replace("_CLASS_SNAKE_CASE_", p_class_name.to_snake_case().validate_identifier()) + .replace("_CLASS_", p_class_name.to_pascal_case().validate_identifier()) .replace("_TS_", _get_indentation()); scr->set_source_code(processed_template); return scr; @@ -190,10 +201,6 @@ bool GDScriptLanguage::validate(const String &p_script, const String &p_path, Li return true; } -bool GDScriptLanguage::has_named_classes() const { - return false; -} - bool GDScriptLanguage::supports_builtin_mode() const { return true; } @@ -500,7 +507,7 @@ String GDScriptLanguage::make_function(const String &p_class, const String &p_na s += p_args[i].get_slice(":", 0); if (th) { String type = p_args[i].get_slice(":", 1); - if (!type.is_empty() && type != "var") { + if (!type.is_empty()) { s += ": " + type; } } @@ -977,7 +984,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, ScriptLanguage::CodeCompletionOption option; switch (member.type) { case GDScriptParser::ClassNode::Member::VARIABLE: - if (p_only_functions || outer || (p_static)) { + if (p_only_functions || outer || (p_static && !member.variable->is_static)) { continue; } option = ScriptLanguage::CodeCompletionOption(member.variable->identifier->name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); @@ -1119,6 +1126,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base base_type.script_type = base_script; } else { base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.builtin_type = Variant::OBJECT; base_type.native_type = scr->get_instance_base_type(); } } else { @@ -1285,7 +1293,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context static const char *_keywords_with_space[] = { "and", "not", "or", "in", "as", "class", "class_name", "extends", "is", "func", "signal", "await", - "const", "enum", "static", "var", "if", "elif", "else", "for", "match", "while", + "const", "enum", "static", "var", "if", "elif", "else", "for", "match", "when", "while", nullptr }; @@ -1459,8 +1467,13 @@ 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); - if (p_expression->get_datatype().kind == GDScriptParser::DataType::ENUM) { - r_type.type = p_expression->get_datatype(); + switch (p_expression->get_datatype().kind) { + case GDScriptParser::DataType::ENUM: + case GDScriptParser::DataType::CLASS: + r_type.type = p_expression->get_datatype(); + break; + default: + break; } found = true; } else { @@ -1618,6 +1631,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, native_type.script_type = parent; } else { native_type.kind = GDScriptParser::DataType::NATIVE; + native_type.builtin_type = Variant::OBJECT; native_type.native_type = native_type.script_type->get_instance_base_type(); if (!ClassDB::class_exists(native_type.native_type)) { native_type.kind = GDScriptParser::DataType::UNRESOLVED; @@ -2147,6 +2161,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, if (ClassDB::class_exists(p_identifier->name) && ClassDB::is_class_exposed(p_identifier->name)) { r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.kind = GDScriptParser::DataType::NATIVE; + r_type.type.builtin_type = Variant::OBJECT; r_type.type.native_type = p_identifier->name; r_type.type.is_constant = true; if (Engine::get_singleton()->has_singleton(p_identifier->name)) { @@ -2183,7 +2198,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & } return true; case GDScriptParser::ClassNode::Member::VARIABLE: - if (!is_static) { + if (!is_static || member.variable->is_static) { if (member.variable->get_datatype().is_set() && !member.variable->get_datatype().is_variant()) { r_type.type = member.variable->get_datatype(); return true; @@ -2258,21 +2273,25 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & return true; } - if (!is_static) { - List<PropertyInfo> members; + List<PropertyInfo> members; + if (is_static) { + scr->get_property_list(&members); + } else { scr->get_script_property_list(&members); - for (const PropertyInfo &prop : members) { - if (prop.name == p_identifier) { - r_type = _type_from_property(prop); - return true; - } + } + for (const PropertyInfo &prop : members) { + if (prop.name == p_identifier) { + r_type = _type_from_property(prop); + return true; } } + Ref<Script> parent = scr->get_base_script(); if (parent.is_valid()) { base_type.script_type = parent; } else { base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.builtin_type = Variant::OBJECT; base_type.native_type = scr->get_instance_base_type(); } } else { @@ -2442,6 +2461,7 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex base_type.script_type = base_script; } else { base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.builtin_type = Variant::OBJECT; base_type.native_type = scr->get_instance_base_type(); } } else { @@ -2697,8 +2717,8 @@ static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, co } r_base_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_base_type.kind = GDScriptParser::DataType::NATIVE; - r_base_type.native_type = node->get_class_name(); r_base_type.builtin_type = Variant::OBJECT; + r_base_type.native_type = node->get_class_name(); return true; } } @@ -3252,6 +3272,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co base_type.script_type = base_script; } else { base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.builtin_type = Variant::OBJECT; base_type.native_type = scr->get_instance_base_type(); } } else { @@ -3388,6 +3409,12 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } } + if ("Variant" == p_symbol) { + r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS; + r_result.class_name = "Variant"; + return OK; + } + if ("PI" == p_symbol || "TAU" == p_symbol || "INF" == p_symbol || "NAN" == p_symbol) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; r_result.class_name = "@GDScript"; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index a6b4dc7981..372c212d2b 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -32,14 +32,6 @@ #include "gdscript.h" -const int *GDScriptFunction::get_code() const { - return _code_ptr; -} - -int GDScriptFunction::get_code_size() const { - return _code_size; -} - Variant GDScriptFunction::get_constant(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, constants.size(), "<errconst>"); return constants[p_idx]; @@ -50,32 +42,6 @@ StringName GDScriptFunction::get_global_name(int p_idx) const { return global_names[p_idx]; } -int GDScriptFunction::get_default_argument_count() const { - return _default_arg_count; -} - -int GDScriptFunction::get_default_argument_addr(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx, default_arguments.size(), -1); - return default_arguments[p_idx]; -} - -GDScriptDataType GDScriptFunction::get_return_type() const { - return return_type; -} - -GDScriptDataType GDScriptFunction::get_argument_type(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx, argument_types.size(), GDScriptDataType()); - return argument_types[p_idx]; -} - -StringName GDScriptFunction::get_name() const { - return name; -} - -int GDScriptFunction::get_max_stack_size() const { - return _stack_size; -} - struct _GDFKC { int order = 0; List<int> pos; @@ -161,9 +127,7 @@ GDScriptFunction::~GDScriptFunction() { return_type.script_type_ref = Ref<Script>(); #ifdef DEBUG_ENABLED - MutexLock lock(GDScriptLanguage::get_singleton()->mutex); - GDScriptLanguage::get_singleton()->function_list.remove(&function_list); #endif } @@ -176,7 +140,7 @@ Variant GDScriptFunctionState::_signal_callback(const Variant **p_args, int p_ar if (p_argcount == 0) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; + r_error.expected = 1; return Variant(); } else if (p_argcount == 1) { //noooneee @@ -224,7 +188,7 @@ bool GDScriptFunctionState::is_valid(bool p_extended_check) const { } Variant GDScriptFunctionState::resume(const Variant &p_arg) { - ERR_FAIL_COND_V(!function, Variant()); + ERR_FAIL_NULL_V(function, Variant()); { MutexLock lock(GDScriptLanguage::singleton->mutex); diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 5230773c13..c9b543fbb9 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -147,39 +147,12 @@ public: return false; } - operator PropertyInfo() const { - PropertyInfo info; - info.usage = PROPERTY_USAGE_NONE; - if (has_type) { - switch (kind) { - case UNINITIALIZED: - break; - case BUILTIN: { - info.type = builtin_type; - } break; - case NATIVE: { - info.type = Variant::OBJECT; - info.class_name = native_type; - } break; - case SCRIPT: - case GDSCRIPT: { - info.type = Variant::OBJECT; - info.class_name = script_type->get_instance_base_type(); - } break; - } - } else { - info.type = Variant::NIL; - info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - } - return info; - } - 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()); + ERR_FAIL_NULL_V(container_element_type, GDScriptDataType()); return *container_element_type; } @@ -268,45 +241,8 @@ public: OPCODE_CALL_METHOD_BIND_RET, OPCODE_CALL_BUILTIN_STATIC, OPCODE_CALL_NATIVE_STATIC, - // ptrcall have one instruction per return type. - OPCODE_CALL_PTRCALL_NO_RETURN, - OPCODE_CALL_PTRCALL_BOOL, - OPCODE_CALL_PTRCALL_INT, - OPCODE_CALL_PTRCALL_FLOAT, - OPCODE_CALL_PTRCALL_STRING, - OPCODE_CALL_PTRCALL_VECTOR2, - OPCODE_CALL_PTRCALL_VECTOR2I, - OPCODE_CALL_PTRCALL_RECT2, - OPCODE_CALL_PTRCALL_RECT2I, - OPCODE_CALL_PTRCALL_VECTOR3, - OPCODE_CALL_PTRCALL_VECTOR3I, - OPCODE_CALL_PTRCALL_TRANSFORM2D, - OPCODE_CALL_PTRCALL_VECTOR4, - OPCODE_CALL_PTRCALL_VECTOR4I, - OPCODE_CALL_PTRCALL_PLANE, - OPCODE_CALL_PTRCALL_QUATERNION, - OPCODE_CALL_PTRCALL_AABB, - OPCODE_CALL_PTRCALL_BASIS, - OPCODE_CALL_PTRCALL_TRANSFORM3D, - OPCODE_CALL_PTRCALL_PROJECTION, - OPCODE_CALL_PTRCALL_COLOR, - OPCODE_CALL_PTRCALL_STRING_NAME, - OPCODE_CALL_PTRCALL_NODE_PATH, - OPCODE_CALL_PTRCALL_RID, - OPCODE_CALL_PTRCALL_OBJECT, - OPCODE_CALL_PTRCALL_CALLABLE, - OPCODE_CALL_PTRCALL_SIGNAL, - OPCODE_CALL_PTRCALL_DICTIONARY, - OPCODE_CALL_PTRCALL_ARRAY, - OPCODE_CALL_PTRCALL_PACKED_BYTE_ARRAY, - OPCODE_CALL_PTRCALL_PACKED_INT32_ARRAY, - OPCODE_CALL_PTRCALL_PACKED_INT64_ARRAY, - OPCODE_CALL_PTRCALL_PACKED_FLOAT32_ARRAY, - OPCODE_CALL_PTRCALL_PACKED_FLOAT64_ARRAY, - OPCODE_CALL_PTRCALL_PACKED_STRING_ARRAY, - OPCODE_CALL_PTRCALL_PACKED_VECTOR2_ARRAY, - OPCODE_CALL_PTRCALL_PACKED_VECTOR3_ARRAY, - OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY, + OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN, + OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN, OPCODE_AWAIT, OPCODE_AWAIT_RESUME, OPCODE_CREATE_LAMBDA, @@ -437,59 +373,31 @@ private: friend class GDScript; friend class GDScriptCompiler; friend class GDScriptByteCodeGenerator; + friend class GDScriptLanguage; + StringName name; StringName source; + bool _static = false; + Vector<GDScriptDataType> argument_types; + GDScriptDataType return_type; + MethodInfo method_info; + Variant rpc_config; - mutable Variant nil; - mutable Variant *_constants_ptr = nullptr; - int _constant_count = 0; - const StringName *_global_names_ptr = nullptr; - int _global_names_count = 0; - const int *_default_arg_ptr = nullptr; - int _default_arg_count = 0; - int _operator_funcs_count = 0; - const Variant::ValidatedOperatorEvaluator *_operator_funcs_ptr = nullptr; - int _setters_count = 0; - const Variant::ValidatedSetter *_setters_ptr = nullptr; - int _getters_count = 0; - const Variant::ValidatedGetter *_getters_ptr = nullptr; - int _keyed_setters_count = 0; - const Variant::ValidatedKeyedSetter *_keyed_setters_ptr = nullptr; - int _keyed_getters_count = 0; - const Variant::ValidatedKeyedGetter *_keyed_getters_ptr = nullptr; - int _indexed_setters_count = 0; - const Variant::ValidatedIndexedSetter *_indexed_setters_ptr = nullptr; - int _indexed_getters_count = 0; - const Variant::ValidatedIndexedGetter *_indexed_getters_ptr = nullptr; - int _builtin_methods_count = 0; - const Variant::ValidatedBuiltInMethod *_builtin_methods_ptr = nullptr; - int _constructors_count = 0; - const Variant::ValidatedConstructor *_constructors_ptr = nullptr; - int _utilities_count = 0; - const Variant::ValidatedUtilityFunction *_utilities_ptr = nullptr; - int _gds_utilities_count = 0; - const GDScriptUtilityFunctions::FunctionPtr *_gds_utilities_ptr = nullptr; - int _methods_count = 0; - MethodBind **_methods_ptr = nullptr; - int _lambdas_count = 0; - GDScriptFunction **_lambdas_ptr = nullptr; - int *_code_ptr = nullptr; - int _code_size = 0; + GDScript *_script = nullptr; + int _initial_line = 0; int _argument_count = 0; int _stack_size = 0; int _instruction_args_size = 0; - int _ptrcall_args_size = 0; - - int _initial_line = 0; - bool _static = false; - Variant rpc_config; - GDScript *_script = nullptr; + SelfList<GDScriptFunction> function_list{ this }; + mutable Variant nil; + HashMap<int, Variant::Type> temporary_slots; + List<StackDebug> stack_debug; - StringName name; + Vector<int> code; + Vector<int> default_arguments; Vector<Variant> constants; Vector<StringName> global_names; - Vector<int> default_arguments; Vector<Variant::ValidatedOperatorEvaluator> operator_funcs; Vector<Variant::ValidatedSetter> setters; Vector<Variant::ValidatedGetter> getters; @@ -503,18 +411,47 @@ private: Vector<GDScriptUtilityFunctions::FunctionPtr> gds_utilities; Vector<MethodBind *> methods; Vector<GDScriptFunction *> lambdas; - Vector<int> code; - Vector<GDScriptDataType> argument_types; - GDScriptDataType return_type; - HashMap<int, Variant::Type> temporary_slots; + int _code_size = 0; + int _default_arg_count = 0; + int _constant_count = 0; + int _global_names_count = 0; + int _operator_funcs_count = 0; + int _setters_count = 0; + int _getters_count = 0; + int _keyed_setters_count = 0; + int _keyed_getters_count = 0; + int _indexed_setters_count = 0; + int _indexed_getters_count = 0; + int _builtin_methods_count = 0; + int _constructors_count = 0; + int _utilities_count = 0; + int _gds_utilities_count = 0; + int _methods_count = 0; + int _lambdas_count = 0; -#ifdef TOOLS_ENABLED - Vector<StringName> arg_names; - Vector<Variant> default_arg_values; -#endif + int *_code_ptr = nullptr; + const int *_default_arg_ptr = nullptr; + mutable Variant *_constants_ptr = nullptr; + const StringName *_global_names_ptr = nullptr; + const Variant::ValidatedOperatorEvaluator *_operator_funcs_ptr = nullptr; + const Variant::ValidatedSetter *_setters_ptr = nullptr; + const Variant::ValidatedGetter *_getters_ptr = nullptr; + const Variant::ValidatedKeyedSetter *_keyed_setters_ptr = nullptr; + const Variant::ValidatedKeyedGetter *_keyed_getters_ptr = nullptr; + const Variant::ValidatedIndexedSetter *_indexed_setters_ptr = nullptr; + const Variant::ValidatedIndexedGetter *_indexed_getters_ptr = nullptr; + const Variant::ValidatedBuiltInMethod *_builtin_methods_ptr = nullptr; + const Variant::ValidatedConstructor *_constructors_ptr = nullptr; + const Variant::ValidatedUtilityFunction *_utilities_ptr = nullptr; + const GDScriptUtilityFunctions::FunctionPtr *_gds_utilities_ptr = nullptr; + MethodBind **_methods_ptr = nullptr; + GDScriptFunction **_lambdas_ptr = nullptr; #ifdef DEBUG_ENABLED + CharString func_cname; + const char *_func_cname = nullptr; + Vector<String> operator_names; Vector<String> setter_names; Vector<String> getter_names; @@ -522,20 +459,6 @@ private: Vector<String> constructors_names; Vector<String> utilities_names; Vector<String> gds_utilities_names; -#endif - - List<StackDebug> stack_debug; - - Variant _get_default_variant_for_data_type(const GDScriptDataType &p_data_type); - - _FORCE_INLINE_ String _get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const; - - friend class GDScriptLanguage; - - SelfList<GDScriptFunction> function_list{ this }; -#ifdef DEBUG_ENABLED - CharString func_cname; - const char *_func_cname = nullptr; struct Profile { StringName signature; @@ -549,9 +472,11 @@ private: uint64_t last_frame_self_time = 0; uint64_t last_frame_total_time = 0; } profile; - #endif + _FORCE_INLINE_ String _get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const; + Variant _get_default_variant_for_data_type(const GDScriptDataType &p_data_type); + public: static constexpr int MAX_CALL_DEPTH = 2048; // Limit to try to avoid crash because of a stack overflow. @@ -571,51 +496,24 @@ public: Variant result; }; + _FORCE_INLINE_ StringName get_name() const { return name; } + _FORCE_INLINE_ StringName get_source() const { return source; } + _FORCE_INLINE_ GDScript *get_script() const { return _script; } _FORCE_INLINE_ bool is_static() const { return _static; } + _FORCE_INLINE_ MethodInfo get_method_info() const { return method_info; } + _FORCE_INLINE_ Variant get_rpc_config() const { return rpc_config; } + _FORCE_INLINE_ int get_max_stack_size() const { return _stack_size; } - const int *get_code() const; //used for debug - int get_code_size() const; Variant get_constant(int p_idx) const; StringName get_global_name(int p_idx) const; - StringName get_name() const; - int get_max_stack_size() const; - int get_default_argument_count() const; - int get_default_argument_addr(int p_idx) const; - GDScriptDataType get_return_type() const; - GDScriptDataType get_argument_type(int p_idx) const; - GDScript *get_script() const { return _script; } - StringName get_source() const { return source; } - - void debug_get_stack_member_state(int p_line, List<Pair<StringName, int>> *r_stackvars) const; - - _FORCE_INLINE_ bool is_empty() const { return _code_size == 0; } - - int get_argument_count() const { return _argument_count; } - StringName get_argument_name(int p_idx) const { -#ifdef TOOLS_ENABLED - ERR_FAIL_INDEX_V(p_idx, arg_names.size(), StringName()); - return arg_names[p_idx]; -#else - return StringName(); -#endif - } - Variant get_default_argument(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx, default_arguments.size(), Variant()); - return default_arguments[p_idx]; - } -#ifdef TOOLS_ENABLED - const Vector<Variant> &get_default_arg_values() const { - return default_arg_values; - } -#endif // TOOLS_ENABLED Variant call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state = nullptr); + void debug_get_stack_member_state(int p_line, List<Pair<StringName, int>> *r_stackvars) const; #ifdef DEBUG_ENABLED void disassemble(const Vector<String> &p_code_lines) const; #endif - _FORCE_INLINE_ const Variant get_rpc_config() const { return rpc_config; } GDScriptFunction(); ~GDScriptFunction(); }; diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp index 3b89f077bd..547f5607d3 100644 --- a/modules/gdscript/gdscript_lambda_callable.cpp +++ b/modules/gdscript/gdscript_lambda_callable.cpp @@ -44,11 +44,18 @@ bool GDScriptLambdaCallable::compare_less(const CallableCustom *p_a, const Calla return p_a < p_b; } +bool GDScriptLambdaCallable::is_valid() const { + return CallableCustom::is_valid() && function != nullptr; +} + uint32_t GDScriptLambdaCallable::hash() const { return h; } String GDScriptLambdaCallable::get_as_text() const { + if (function == nullptr) { + return "<invalid lambda>"; + } if (function->get_name() != StringName()) { return function->get_name().operator String() + "(lambda)"; } @@ -74,29 +81,78 @@ StringName GDScriptLambdaCallable::get_method() const { 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 (function == nullptr) { + r_return_value = Variant(); + r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; + return; + } + 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]; + if (captures[i].get_type() == Variant::OBJECT) { + bool was_freed = false; + captures[i].get_validated_object_with_check(was_freed); + if (was_freed) { + ERR_PRINT(vformat(R"(Lambda capture at index %d was freed. Passed "null" instead.)", i)); + static Variant nil; + args.write[i] = &nil; + } + } } 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; + switch (r_call_error.error) { + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: + r_call_error.argument -= captures_amount; +#ifdef DEBUG_ENABLED + if (r_call_error.argument < 0) { + ERR_PRINT(vformat("GDScript bug (please report): Invalid value of lambda capture at index %d.", captures_amount + r_call_error.argument)); + r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code. + r_call_error.argument = 0; + r_call_error.expected = 0; + } +#endif + break; + case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: + case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: + r_call_error.expected -= captures_amount; +#ifdef DEBUG_ENABLED + if (r_call_error.expected < 0) { + ERR_PRINT("GDScript bug (please report): Invalid lambda captures count."); + r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code. + r_call_error.argument = 0; + r_call_error.expected = 0; + } +#endif + break; + default: + break; + } } 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) { + ERR_FAIL_NULL(p_script.ptr()); + ERR_FAIL_NULL(p_function); script = p_script; function = p_function; captures = p_captures; h = (uint32_t)hash_murmur3_one_64((uint64_t)this); + + updatable_func_ptr_element = p_script->_add_func_ptr_to_update(&function); +} + +GDScriptLambdaCallable::~GDScriptLambdaCallable() { + GDScript::_remove_func_ptr_to_update(updatable_func_ptr_element); } bool GDScriptLambdaSelfCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) { @@ -109,11 +165,18 @@ bool GDScriptLambdaSelfCallable::compare_less(const CallableCustom *p_a, const C return p_a < p_b; } +bool GDScriptLambdaSelfCallable::is_valid() const { + return CallableCustom::is_valid() && function != nullptr; +} + uint32_t GDScriptLambdaSelfCallable::hash() const { return h; } String GDScriptLambdaSelfCallable::get_as_text() const { + if (function == nullptr) { + return "<invalid lambda>"; + } if (function->get_name() != StringName()) { return function->get_name().operator String() + "(lambda)"; } @@ -143,36 +206,95 @@ void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcoun int captures_amount = captures.size(); + if (function == nullptr) { + r_return_value = Variant(); + r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; + return; + } + 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]; + if (captures[i].get_type() == Variant::OBJECT) { + bool was_freed = false; + captures[i].get_validated_object_with_check(was_freed); + if (was_freed) { + ERR_PRINT(vformat(R"(Lambda capture at index %d was freed. Passed "null" instead.)", i)); + static Variant nil; + args.write[i] = &nil; + } + } } for (int i = 0; i < p_argcount; i++) { args.write[i + captures_amount] = p_arguments[i]; } r_return_value = function->call(static_cast<GDScriptInstance *>(object->get_script_instance()), args.ptrw(), args.size(), r_call_error); - r_call_error.argument -= captures_amount; + switch (r_call_error.error) { + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: + r_call_error.argument -= captures_amount; +#ifdef DEBUG_ENABLED + if (r_call_error.argument < 0) { + ERR_PRINT(vformat("GDScript bug (please report): Invalid value of lambda capture at index %d.", captures_amount + r_call_error.argument)); + r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code. + r_call_error.argument = 0; + r_call_error.expected = 0; + } +#endif + break; + case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: + case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: + r_call_error.expected -= captures_amount; +#ifdef DEBUG_ENABLED + if (r_call_error.expected < 0) { + ERR_PRINT("GDScript bug (please report): Invalid lambda captures count."); + r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // TODO: Add a more suitable error code. + r_call_error.argument = 0; + r_call_error.expected = 0; + } +#endif + break; + default: + break; + } } else { r_return_value = function->call(static_cast<GDScriptInstance *>(object->get_script_instance()), p_arguments, p_argcount, r_call_error); } } GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) { + ERR_FAIL_NULL(p_self.ptr()); + ERR_FAIL_NULL(p_function); reference = p_self; object = p_self.ptr(); function = p_function; captures = p_captures; h = (uint32_t)hash_murmur3_one_64((uint64_t)this); + + GDScript *gds = p_function->get_script(); + if (gds != nullptr) { + updatable_func_ptr_element = gds->_add_func_ptr_to_update(&function); + } } GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Object *p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) { + ERR_FAIL_NULL(p_self); + ERR_FAIL_NULL(p_function); object = p_self; function = p_function; captures = p_captures; h = (uint32_t)hash_murmur3_one_64((uint64_t)this); + + GDScript *gds = p_function->get_script(); + if (gds != nullptr) { + updatable_func_ptr_element = gds->_add_func_ptr_to_update(&function); + } +} + +GDScriptLambdaSelfCallable::~GDScriptLambdaSelfCallable() { + GDScript::_remove_func_ptr_to_update(updatable_func_ptr_element); } diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h index 1c7a18fb9d..ee7d547544 100644 --- a/modules/gdscript/gdscript_lambda_callable.h +++ b/modules/gdscript/gdscript_lambda_callable.h @@ -31,12 +31,13 @@ #ifndef GDSCRIPT_LAMBDA_CALLABLE_H #define GDSCRIPT_LAMBDA_CALLABLE_H +#include "gdscript.h" + #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; @@ -44,6 +45,7 @@ class GDScriptLambdaCallable : public CallableCustom { GDScriptFunction *function = nullptr; Ref<GDScript> script; uint32_t h; + GDScript::UpdatableFuncPtrElement updatable_func_ptr_element; Vector<Variant> captures; @@ -51,6 +53,7 @@ class GDScriptLambdaCallable : public CallableCustom { static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b); public: + bool is_valid() const override; uint32_t hash() const override; String get_as_text() const override; CompareEqualFunc get_compare_equal_func() const override; @@ -60,7 +63,7 @@ public: 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; + virtual ~GDScriptLambdaCallable(); }; // Lambda callable that references a particular object, so it can use `self` in the body. @@ -69,6 +72,7 @@ class GDScriptLambdaSelfCallable : public CallableCustom { Ref<RefCounted> reference; // For objects that are RefCounted, keep a reference. Object *object = nullptr; // For non RefCounted objects, use a direct pointer. uint32_t h; + GDScript::UpdatableFuncPtrElement updatable_func_ptr_element; Vector<Variant> captures; @@ -76,6 +80,7 @@ class GDScriptLambdaSelfCallable : public CallableCustom { static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b); public: + bool is_valid() const override; uint32_t hash() const override; String get_as_text() const override; CompareEqualFunc get_compare_equal_func() const override; @@ -85,7 +90,7 @@ public: GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures); GDScriptLambdaSelfCallable(Object *p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures); - virtual ~GDScriptLambdaSelfCallable() = default; + virtual ~GDScriptLambdaSelfCallable(); }; #endif // GDSCRIPT_LAMBDA_CALLABLE_H diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index a0d02b12b5..db7b3e7ace 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -32,10 +32,6 @@ #include "gdscript.h" -#ifdef DEBUG_ENABLED -#include "gdscript_warning.h" -#endif - #include "core/config/project_settings.h" #include "core/io/file_access.h" #include "core/io/resource_loader.h" @@ -52,11 +48,18 @@ #include "editor/editor_settings.h" #endif +// This function is used to determine that a type is "built-in" as opposed to native +// and custom classes. So `Variant::NIL` and `Variant::OBJECT` are excluded: +// `Variant::NIL` - `null` is literal, not a type. +// `Variant::OBJECT` - `Object` should be treated as a class, not as a built-in type. static HashMap<StringName, Variant::Type> builtin_types; Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { - if (builtin_types.is_empty()) { - for (int i = 1; i < Variant::VARIANT_MAX; i++) { - builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i; + if (unlikely(builtin_types.is_empty())) { + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + Variant::Type type = (Variant::Type)i; + if (type != Variant::NIL && type != Variant::OBJECT) { + builtin_types[Variant::get_type_name(type)] = type; + } } } @@ -170,7 +173,7 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) { #ifdef DEBUG_ENABLED void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) { - ERR_FAIL_COND(p_source == nullptr); + ERR_FAIL_NULL(p_source); if (is_ignoring_warnings) { return; } @@ -383,8 +386,10 @@ GDScriptTokenizer::Token GDScriptParser::advance() { push_error(current.literal); current = tokenizer.scan(); } - for (Node *n : nodes_in_progress) { - update_extents(n); + if (previous.type != GDScriptTokenizer::Token::DEDENT) { // `DEDENT` belongs to the next non-empty line. + for (Node *n : nodes_in_progress) { + update_extents(n); + } } return previous; } @@ -510,7 +515,7 @@ void GDScriptParser::parse_program() { if (annotation->applies_to(AnnotationInfo::SCRIPT)) { // `@icon` needs to be applied in the parser. See GH-72444. if (annotation->name == SNAME("@icon")) { - annotation->apply(this, head); + annotation->apply(this, head, nullptr); } else { head->annotations.push_back(annotation); } @@ -579,13 +584,14 @@ void GDScriptParser::parse_program() { complete_extents(head); #ifdef TOOLS_ENABLED - for (const KeyValue<int, GDScriptTokenizer::CommentData> &E : tokenizer.get_comments()) { - if (E.value.new_line && E.value.comment.begins_with("##")) { - class_doc_line = MIN(class_doc_line, E.key); + const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); + int line = MIN(max_script_doc_line, head->end_line); + while (line > 0) { + if (comments.has(line) && comments[line].new_line && comments[line].comment.begins_with("##")) { + head->doc_data = parse_class_doc_comment(line); + break; } - } - if (has_comment(class_doc_line, true)) { - head->doc_data = parse_class_doc_comment(class_doc_line, false); + line--; } #endif // TOOLS_ENABLED @@ -747,10 +753,6 @@ template <class T> void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static) { advance(); -#ifdef TOOLS_ENABLED - int doc_comment_line = previous.start_line - 1; -#endif // TOOLS_ENABLED - // Consume annotations. List<AnnotationNode *> annotations; while (!annotation_stack.is_empty()) { @@ -762,11 +764,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b push_error(vformat(R"(Annotation "%s" cannot be applied to a %s.)", last_annotation->name, p_member_kind)); clear_unused_annotations(); } -#ifdef TOOLS_ENABLED - if (last_annotation->start_line == doc_comment_line) { - doc_comment_line--; - } -#endif // TOOLS_ENABLED } T *member = (this->*p_parse_function)(p_is_static); @@ -774,28 +771,40 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b return; } +#ifdef TOOLS_ENABLED + int doc_comment_line = member->start_line - 1; +#endif // TOOLS_ENABLED + for (AnnotationNode *&annotation : annotations) { member->annotations.push_back(annotation); +#ifdef TOOLS_ENABLED + if (annotation->start_line <= doc_comment_line) { + doc_comment_line = annotation->start_line - 1; + } +#endif // TOOLS_ENABLED } #ifdef TOOLS_ENABLED - // Consume doc comments. - class_doc_line = MIN(class_doc_line, doc_comment_line - 1); - - // Check whether current line has a doc comment - if (has_comment(previous.start_line, true)) { - if constexpr (std::is_same_v<T, ClassNode>) { - member->doc_data = parse_class_doc_comment(previous.start_line, true, true); - } else { - member->doc_data = parse_doc_comment(previous.start_line, true); + if constexpr (std::is_same_v<T, ClassNode>) { + if (has_comment(member->start_line, true)) { + // Inline doc comment. + member->doc_data = parse_class_doc_comment(member->start_line, true); + } else if (has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) { + // Normal doc comment. Don't check `min_member_doc_line` because a class ends parsing after its members. + // This may not work correctly for cases like `var a; class B`, but it doesn't matter in practice. + member->doc_data = parse_class_doc_comment(doc_comment_line); } - } else if (has_comment(doc_comment_line, true)) { - if constexpr (std::is_same_v<T, ClassNode>) { - member->doc_data = parse_class_doc_comment(doc_comment_line, true); - } else { + } else { + if (has_comment(member->start_line, true)) { + // Inline doc comment. + member->doc_data = parse_doc_comment(member->start_line, true); + } else if (doc_comment_line >= min_member_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) { + // Normal doc comment. member->doc_data = parse_doc_comment(doc_comment_line); } } + + min_member_doc_line = member->end_line + 1; // Prevent multiple members from using the same doc comment. #endif // TOOLS_ENABLED if (member->identifier != nullptr) { @@ -1263,6 +1272,9 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) { push_multiline(true); consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")")); +#ifdef TOOLS_ENABLED + int min_enum_value_doc_line = previous.end_line + 1; +#endif HashMap<StringName, int> elements; @@ -1325,43 +1337,35 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) { } } while (match(GDScriptTokenizer::Token::COMMA)); - pop_multiline(); - consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)"); - #ifdef TOOLS_ENABLED // Enum values documentation. for (int i = 0; i < enum_node->values.size(); i++) { - int doc_comment_line = enum_node->values[i].line; - bool single_line = false; - - if (has_comment(doc_comment_line, true)) { - single_line = true; - } else if (has_comment(doc_comment_line - 1, true)) { - doc_comment_line--; - } else { - continue; - } - - if (i == enum_node->values.size() - 1) { - // If close bracket is same line as last value. - if (doc_comment_line == previous.start_line) { - break; - } - } else { - // If two values are same line. - if (doc_comment_line == enum_node->values[i + 1].line) { - continue; + int enum_value_line = enum_node->values[i].line; + int doc_comment_line = enum_value_line - 1; + + MemberDocData doc_data; + if (has_comment(enum_value_line, true)) { + // Inline doc comment. + if (i == enum_node->values.size() - 1 || enum_node->values[i + 1].line > enum_value_line) { + doc_data = parse_doc_comment(enum_value_line, true); } + } else if (doc_comment_line >= min_enum_value_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) { + // Normal doc comment. + doc_data = parse_doc_comment(doc_comment_line); } if (named) { - enum_node->values.write[i].doc_data = parse_doc_comment(doc_comment_line, single_line); + enum_node->values.write[i].doc_data = doc_data; } else { - current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, parse_doc_comment(doc_comment_line, single_line)); + current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, doc_data); } + + min_enum_value_doc_line = enum_value_line + 1; // Prevent multiple enum values from using the same doc comment. } #endif // TOOLS_ENABLED + pop_multiline(); + consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)"); complete_extents(enum_node); end_statement("enum"); @@ -2034,7 +2038,37 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { push_error(R"(No pattern found for "match" branch.)"); } - if (!consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)")) { + bool has_guard = false; + if (match(GDScriptTokenizer::Token::WHEN)) { + // Pattern guard. + // Create block for guard because it also needs to access the bound variables from patterns, and we don't want to add them to the outer scope. + branch->guard_body = alloc_node<SuiteNode>(); + if (branch->patterns.size() > 0) { + for (const KeyValue<StringName, IdentifierNode *> &E : branch->patterns[0]->binds) { + SuiteNode::Local local(E.value, current_function); + local.type = SuiteNode::Local::PATTERN_BIND; + branch->guard_body->add_local(local); + } + } + + SuiteNode *parent_block = current_suite; + branch->guard_body->parent_block = parent_block; + current_suite = branch->guard_body; + + ExpressionNode *guard = parse_expression(false); + if (guard == nullptr) { + push_error(R"(Expected expression for pattern guard after "when".)"); + } else { + branch->guard_body->statements.append(guard); + } + current_suite = parent_block; + complete_extents(branch->guard_body); + + has_guard = true; + branch->has_wildcard = false; // If it has a guard, the wildcard might still not match. + } + + if (!consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":"%s after "match" %s.)", has_guard ? "" : R"( or "when")", has_guard ? "pattern guard" : "patterns"))) { complete_extents(branch); return nullptr; } @@ -3454,31 +3488,21 @@ bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) { } GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool p_single_line) { - MemberDocData result; + ERR_FAIL_COND_V(!has_comment(p_line, true), MemberDocData()); const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); - ERR_FAIL_COND_V(!comments.has(p_line), result); - - if (p_single_line) { - if (comments[p_line].comment.begins_with("##")) { - result.description = comments[p_line].comment.trim_prefix("##").strip_edges(); - return result; - } - return result; - } - int line = p_line; - DocLineState state = DOC_LINE_NORMAL; - while (comments.has(line - 1)) { - if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) { - break; + if (!p_single_line) { + while (comments.has(line - 1) && comments[line - 1].new_line && comments[line - 1].comment.begins_with("##")) { + line--; } - line--; } + max_script_doc_line = MIN(max_script_doc_line, line - 1); + String space_prefix; - if (comments.has(line) && comments[line].comment.begins_with("##")) { + { int i = 2; for (; i < comments[line].comment.length(); i++) { if (comments[line].comment[i] != ' ') { @@ -3488,11 +3512,10 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool space_prefix = String(" ").repeat(i - 2); } - while (comments.has(line)) { - if (!comments[line].new_line || !comments[line].comment.begins_with("##")) { - break; - } + DocLineState state = DOC_LINE_NORMAL; + MemberDocData result; + while (line <= p_line) { String doc_line = comments[line].comment.trim_prefix("##"); line++; @@ -3513,35 +3536,22 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool return result; } -GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_inner_class, bool p_single_line) { - ClassDocData result; +GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_single_line) { + ERR_FAIL_COND_V(!has_comment(p_line, true), ClassDocData()); const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments(); - ERR_FAIL_COND_V(!comments.has(p_line), result); - - if (p_single_line) { - if (comments[p_line].comment.begins_with("##")) { - result.brief = comments[p_line].comment.trim_prefix("##").strip_edges(); - return result; - } - return result; - } - int line = p_line; - DocLineState state = DOC_LINE_NORMAL; - bool is_in_brief = true; - if (p_inner_class) { - while (comments.has(line - 1)) { - if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) { - break; - } + if (!p_single_line) { + while (comments.has(line - 1) && comments[line - 1].new_line && comments[line - 1].comment.begins_with("##")) { line--; } } + max_script_doc_line = MIN(max_script_doc_line, line - 1); + String space_prefix; - if (comments.has(line) && comments[line].comment.begins_with("##")) { + { int i = 2; for (; i < comments[line].comment.length(); i++) { if (comments[line].comment[i] != ' ') { @@ -3551,11 +3561,11 @@ GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, space_prefix = String(" ").repeat(i - 2); } - while (comments.has(line)) { - if (!comments[line].new_line || !comments[line].comment.begins_with("##")) { - break; - } + DocLineState state = DOC_LINE_NORMAL; + bool is_in_brief = true; + ClassDocData result; + while (line <= p_line) { String doc_line = comments[line].comment.trim_prefix("##"); line++; @@ -3630,14 +3640,6 @@ GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, } } - if (current_class->members.size() > 0) { - const ClassNode::Member &m = current_class->members[0]; - int first_member_line = m.get_line(); - if (first_member_line == line) { - result = ClassDocData(); // Clear result. - } - } - return result; } #endif // TOOLS_ENABLED @@ -3705,6 +3707,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty { nullptr, nullptr, PREC_NONE }, // PASS, { nullptr, nullptr, PREC_NONE }, // RETURN, { nullptr, nullptr, PREC_NONE }, // MATCH, + { nullptr, nullptr, PREC_NONE }, // WHEN, // Keywords { nullptr, &GDScriptParser::parse_cast, PREC_CAST }, // AS, { nullptr, nullptr, PREC_NONE }, // ASSERT, @@ -3788,12 +3791,12 @@ const GDScriptParser::SuiteNode::Local &GDScriptParser::SuiteNode::get_local(con return empty; } -bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target) { +bool GDScriptParser::AnnotationNode::apply(GDScriptParser *p_this, Node *p_target, ClassNode *p_class) { if (is_applied) { return true; } is_applied = true; - return (p_this->*(p_this->valid_annotations[name].apply))(this, p_target); + return (p_this->*(p_this->valid_annotations[name].apply))(this, p_target, p_class); } bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const { @@ -3839,7 +3842,7 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) return true; } -bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_node) { +bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { #ifdef DEBUG_ENABLED if (this->_is_tool) { push_error(R"("@tool" annotation can only be used once.)", p_annotation); @@ -3850,15 +3853,15 @@ bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p return true; } -bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_node) { - ERR_FAIL_COND_V_MSG(p_node->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)"); +bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)"); ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false); - ClassNode *p_class = static_cast<ClassNode *>(p_node); + ClassNode *class_node = static_cast<ClassNode *>(p_target); String path = p_annotation->resolved_arguments[0]; #ifdef DEBUG_ENABLED - if (!p_class->icon_path.is_empty()) { + if (!class_node->icon_path.is_empty()) { push_error(R"("@icon" annotation can only be used once.)", p_annotation); return false; } @@ -3868,27 +3871,27 @@ bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p } #endif // DEBUG_ENABLED - p_class->icon_path = path; + class_node->icon_path = path; if (path.is_empty() || path.is_absolute_path()) { - p_class->simplified_icon_path = path.simplify_path(); + class_node->simplified_icon_path = path.simplify_path(); } else if (path.is_relative_path()) { - p_class->simplified_icon_path = script_path.get_base_dir().path_join(path).simplify_path(); + class_node->simplified_icon_path = script_path.get_base_dir().path_join(path).simplify_path(); } else { - p_class->simplified_icon_path = path; + class_node->simplified_icon_path = path; } return true; } -bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_node) { - ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)"); +bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)"); if (current_class && !ClassDB::is_parent_class(current_class->get_datatype().native_type, SNAME("Node"))) { push_error(R"("@onready" can only be used in classes that inherit "Node".)", p_annotation); } - VariableNode *variable = static_cast<VariableNode *>(p_node); + VariableNode *variable = static_cast<VariableNode *>(p_target); if (variable->is_static) { push_error(R"("@onready" annotation cannot be applied to a static variable.)", p_annotation); return false; @@ -3903,10 +3906,11 @@ bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node } template <PropertyHint t_hint, Variant::Type t_type> -bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_node) { - ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); +bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::VARIABLE, false, vformat(R"("%s" annotation can only be applied to variables.)", p_annotation->name)); + ERR_FAIL_NULL_V(p_class, false); - VariableNode *variable = static_cast<VariableNode *>(p_node); + VariableNode *variable = static_cast<VariableNode *>(p_target); if (variable->is_static) { push_error(vformat(R"(Annotation "%s" cannot be applied to a static variable.)", p_annotation->name), p_annotation); return false; @@ -4095,27 +4099,38 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } } break; case GDScriptParser::DataType::ENUM: { - variable->export_info.type = Variant::INT; - variable->export_info.hint = PROPERTY_HINT_ENUM; - - String enum_hint_string; - bool first = true; - for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) { - if (!first) { - enum_hint_string += ","; - } else { - first = false; + if (export_type.is_meta_type) { + variable->export_info.type = Variant::DICTIONARY; + } else { + variable->export_info.type = Variant::INT; + variable->export_info.hint = PROPERTY_HINT_ENUM; + + String enum_hint_string; + bool first = true; + for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) { + if (!first) { + enum_hint_string += ","; + } else { + first = false; + } + enum_hint_string += E.key.operator String().capitalize().xml_escape(); + enum_hint_string += ":"; + enum_hint_string += String::num_int64(E.value).xml_escape(); } - enum_hint_string += E.key.operator String().capitalize().xml_escape(); - enum_hint_string += ":"; - enum_hint_string += String::num_int64(E.value).xml_escape(); - } - variable->export_info.hint_string = enum_hint_string; + variable->export_info.hint_string = enum_hint_string; + variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM; + variable->export_info.class_name = String(export_type.native_type).replace("::", "."); + } } break; default: push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable); - break; + return false; + } + + if (variable->export_info.hint == PROPERTY_HINT_NODE_TYPE && !ClassDB::is_parent_class(p_class->base_type.native_type, SNAME("Node"))) { + push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), variable); + return false; } if (is_array) { @@ -4160,7 +4175,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } template <PropertyUsageFlags t_usage> -bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_node) { +bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { AnnotationNode *annotation = const_cast<AnnotationNode *>(p_annotation); if (annotation->resolved_arguments.is_empty()) { @@ -4192,7 +4207,7 @@ bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation return true; } -bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_node) { +bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { #ifdef DEBUG_ENABLED bool has_error = false; for (const Variant &warning_name : p_annotation->resolved_arguments) { @@ -4201,7 +4216,7 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod push_error(vformat(R"(Invalid warning name: "%s".)", warning_name), p_annotation); has_error = true; } else { - p_node->ignored_warnings.push_back(warning); + p_target->ignored_warnings.push_back(warning); } } @@ -4213,10 +4228,10 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod #endif // DEBUG_ENABLED } -bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_node) { - ERR_FAIL_COND_V_MSG(p_node->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to functions.)", p_annotation->name)); +bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to functions.)", p_annotation->name)); - FunctionNode *function = static_cast<FunctionNode *>(p_node); + FunctionNode *function = static_cast<FunctionNode *>(p_target); if (function->rpc_config.get_type() != Variant::NIL) { push_error(R"(RPC annotations can only be used once per function.)", p_annotation); return false; @@ -4274,14 +4289,14 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_ return true; } -bool GDScriptParser::static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target) { +bool GDScriptParser::static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class) { ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, vformat(R"("%s" annotation can only be applied to classes.)", p_annotation->name)); - ClassNode *p_class = static_cast<ClassNode *>(p_target); - if (p_class->annotated_static_unload) { + ClassNode *class_node = static_cast<ClassNode *>(p_target); + if (class_node->annotated_static_unload) { push_error(vformat(R"("%s" annotation can only be used once per script.)", p_annotation->name), p_annotation); return false; } - p_class->annotated_static_unload = true; + class_node->annotated_static_unload = true; return true; } @@ -4370,6 +4385,104 @@ String GDScriptParser::DataType::to_string() const { ERR_FAIL_V_MSG("<unresolved type>", "Kind set outside the enum range."); } +PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) const { + PropertyInfo result; + result.name = p_name; + result.usage = PROPERTY_USAGE_NONE; + + if (!is_hard_type()) { + result.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + return result; + } + + switch (kind) { + case BUILTIN: + result.type = builtin_type; + if (builtin_type == Variant::ARRAY && has_container_element_type()) { + const DataType *elem_type = container_element_type; + switch (elem_type->kind) { + case BUILTIN: + result.hint = PROPERTY_HINT_ARRAY_TYPE; + result.hint_string = Variant::get_type_name(elem_type->builtin_type); + break; + case NATIVE: + result.hint = PROPERTY_HINT_ARRAY_TYPE; + result.hint_string = elem_type->native_type; + break; + case SCRIPT: + result.hint = PROPERTY_HINT_ARRAY_TYPE; + if (elem_type->script_type.is_valid() && elem_type->script_type->get_global_name() != StringName()) { + result.hint_string = elem_type->script_type->get_global_name(); + } else { + result.hint_string = elem_type->native_type; + } + break; + case CLASS: + result.hint = PROPERTY_HINT_ARRAY_TYPE; + if (elem_type->class_type != nullptr && elem_type->class_type->get_global_name() != StringName()) { + result.hint_string = elem_type->class_type->get_global_name(); + } else { + result.hint_string = elem_type->native_type; + } + break; + case ENUM: + result.hint = PROPERTY_HINT_ARRAY_TYPE; + result.hint_string = String(elem_type->native_type).replace("::", "."); + break; + case VARIANT: + case RESOLVING: + case UNRESOLVED: + break; + } + } + break; + case NATIVE: + result.type = Variant::OBJECT; + if (is_meta_type) { + result.class_name = GDScriptNativeClass::get_class_static(); + } else { + result.class_name = native_type; + } + break; + case SCRIPT: + result.type = Variant::OBJECT; + if (is_meta_type) { + result.class_name = script_type.is_valid() ? script_type->get_class() : Script::get_class_static(); + } else if (script_type.is_valid() && script_type->get_global_name() != StringName()) { + result.class_name = script_type->get_global_name(); + } else { + result.class_name = native_type; + } + break; + case CLASS: + result.type = Variant::OBJECT; + if (is_meta_type) { + result.class_name = GDScript::get_class_static(); + } else if (class_type != nullptr && class_type->get_global_name() != StringName()) { + result.class_name = class_type->get_global_name(); + } else { + result.class_name = native_type; + } + break; + case ENUM: + if (is_meta_type) { + result.type = Variant::DICTIONARY; + } else { + result.type = Variant::INT; + result.usage |= PROPERTY_USAGE_CLASS_IS_ENUM; + result.class_name = String(native_type).replace("::", "."); + } + break; + case VARIANT: + case RESOLVING: + case UNRESOLVED: + result.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + break; + } + + return result; +} + static Variant::Type _variant_type_to_typed_array_element_type(Variant::Type p_type) { switch (p_type) { case Variant::PACKED_BYTE_ARRAY: @@ -5293,7 +5406,7 @@ void GDScriptParser::TreePrinter::print_while(WhileNode *p_while) { } void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) { - ERR_FAIL_COND_MSG(p_parser.get_tree() == nullptr, "Parse the code before printing the parse tree."); + ERR_FAIL_NULL_MSG(p_parser.get_tree(), "Parse the code before printing the parse tree."); if (p_parser.is_tool()) { push_line("@tool"); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 9690784cba..4b46b98baa 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -147,14 +147,17 @@ public: _FORCE_INLINE_ bool has_no_type() const { return type_source == UNDETECTED; } _FORCE_INLINE_ bool is_variant() const { return kind == VARIANT || kind == RESOLVING || kind == UNRESOLVED; } _FORCE_INLINE_ bool is_hard_type() const { return type_source > INFERRED; } + String to_string() const; + _FORCE_INLINE_ String to_string_strict() const { return is_hard_type() ? to_string() : "Variant"; } + PropertyInfo to_property_info(const String &p_name) 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()); + ERR_FAIL_NULL_V(container_element_type, DataType()); return *container_element_type; } @@ -175,11 +178,11 @@ public: 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. + return true; // Can be considered equal for parsing purposes. } if (type_source == INFERRED || p_other.type_source == INFERRED) { - return true; // Can be consireded equal for parsing purposes. + return true; // Can be considered equal for parsing purposes. } if (kind != p_other.kind) { @@ -360,7 +363,7 @@ public: bool is_resolved = false; bool is_applied = false; - bool apply(GDScriptParser *p_this, Node *p_target); + bool apply(GDScriptParser *p_this, Node *p_target, ClassNode *p_class); bool applies_to(uint32_t p_target_kinds) const; AnnotationNode() { @@ -749,6 +752,10 @@ public: bool resolved_interface = false; bool resolved_body = false; + StringName get_global_name() const { + return (outer == nullptr && identifier != nullptr) ? identifier->name : StringName(); + } + Member get_member(const StringName &p_name) const { return members[members_indices[p_name]]; } @@ -831,13 +838,13 @@ public: HashMap<StringName, int> parameters_indices; TypeNode *return_type = nullptr; SuiteNode *body = nullptr; - bool is_static = false; + bool is_static = false; // For lambdas it's determined in the analyzer. bool is_coroutine = false; Variant rpc_config; MethodInfo info; LambdaNode *source_lambda = nullptr; -#ifdef TOOLS_ENABLED Vector<Variant> default_arg_values; +#ifdef TOOLS_ENABLED MemberDocData doc_data; #endif // TOOLS_ENABLED @@ -942,6 +949,7 @@ public: Vector<PatternNode *> patterns; SuiteNode *block = nullptr; bool has_wildcard = false; + SuiteNode *guard_body = nullptr; MatchBranchNode() { type = MATCH_BRANCH; @@ -1026,6 +1034,7 @@ public: IdentifierNode *identifier = nullptr; Vector<ParameterNode *> parameters; HashMap<StringName, int> parameters_indices; + MethodInfo method_info; #ifdef TOOLS_ENABLED MemberDocData doc_data; #endif // TOOLS_ENABLED @@ -1331,7 +1340,7 @@ private: 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); + typedef bool (GDScriptParser::*AnnotationAction)(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); struct AnnotationInfo { enum TargetKind { NONE = 0, @@ -1452,16 +1461,16 @@ private: bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false); bool validate_annotation_arguments(AnnotationNode *p_annotation); void clear_unused_annotations(); - bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target); - bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target); - bool onready_annotation(const AnnotationNode *p_annotation, Node *p_target); + bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool onready_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); template <PropertyHint t_hint, Variant::Type t_type> - bool export_annotations(const AnnotationNode *p_annotation, Node *p_target); + bool export_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); template <PropertyUsageFlags t_usage> - bool export_group_annotations(const AnnotationNode *p_annotation, Node *p_target); - bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target); - bool rpc_annotation(const AnnotationNode *p_annotation, Node *p_target); - bool static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target); + bool export_group_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool rpc_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); + bool static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target, ClassNode *p_class); // Statements. Node *parse_statement(); VariableNode *parse_variable(bool p_is_static); @@ -1510,10 +1519,11 @@ private: TypeNode *parse_type(bool p_allow_void = false); #ifdef TOOLS_ENABLED - int class_doc_line = 0x7FFFFFFF; + int max_script_doc_line = INT_MAX; + int min_member_doc_line = 1; bool has_comment(int p_line, bool p_must_be_doc = false); MemberDocData parse_doc_comment(int p_line, bool p_single_line = false); - ClassDocData parse_class_doc_comment(int p_line, bool p_inner_class, bool p_single_line = false); + ClassDocData parse_class_doc_comment(int p_line, bool p_single_line = false); #endif // TOOLS_ENABLED public: @@ -1522,7 +1532,7 @@ public: bool is_tool() const { return _is_tool; } ClassNode *find_class(const String &p_qualified_name) const; bool has_class(const GDScriptParser::ClassNode *p_class) const; - static Variant::Type get_builtin_type(const StringName &p_type); + static Variant::Type get_builtin_type(const StringName &p_type); // Excluding `Variant::NIL` and `Variant::OBJECT`. CompletionContext get_completion_context() const { return completion_context; } CompletionCall get_completion_call() const { return completion_call; } diff --git a/modules/gdscript/gdscript_rpc_callable.cpp b/modules/gdscript/gdscript_rpc_callable.cpp index 199ea81330..df014d3cfe 100644 --- a/modules/gdscript/gdscript_rpc_callable.cpp +++ b/modules/gdscript/gdscript_rpc_callable.cpp @@ -73,12 +73,13 @@ void GDScriptRPCCallable::call(const Variant **p_arguments, int p_argcount, Vari } GDScriptRPCCallable::GDScriptRPCCallable(Object *p_object, const StringName &p_method) { + ERR_FAIL_NULL(p_object); object = p_object; method = p_method; h = method.hash(); h = hash_murmur3_one_64(object->get_instance_id(), h); node = Object::cast_to<Node>(object); - ERR_FAIL_COND_MSG(!node, "RPC can only be defined on class that extends Node."); + ERR_FAIL_NULL_MSG(node, "RPC can only be defined on class that extends Node."); } Error GDScriptRPCCallable::rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const { diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 42b983ef45..98a3a1268f 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -99,6 +99,7 @@ static const char *token_names[] = { "pass", // PASS, "return", // RETURN, "match", // MATCH, + "when", // WHEN, // Keywords "as", // AS, "assert", // ASSERT, @@ -187,6 +188,7 @@ bool GDScriptTokenizer::Token::is_identifier() const { switch (type) { case IDENTIFIER: case MATCH: // Used in String.match(). + case WHEN: // New keyword, avoid breaking existing code. // Allow constants to be treated as regular identifiers. case CONST_PI: case CONST_INF: @@ -241,6 +243,7 @@ bool GDScriptTokenizer::Token::is_node_name() const { case VAR: case VOID: case WHILE: + case WHEN: case YIELD: return true; default: @@ -531,6 +534,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::annotation() { KEYWORD("void", Token::VOID) \ KEYWORD_GROUP('w') \ KEYWORD("while", Token::WHILE) \ + KEYWORD("when", Token::WHEN) \ KEYWORD_GROUP('y') \ KEYWORD("yield", Token::YIELD) \ KEYWORD_GROUP('I') \ @@ -857,10 +861,14 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { STRING_NODEPATH, }; + bool is_raw = false; bool is_multiline = false; StringType type = STRING_REGULAR; - if (_peek(-1) == '&') { + if (_peek(-1) == 'r') { + is_raw = true; + _advance(); + } else if (_peek(-1) == '&') { type = STRING_NAME; _advance(); } else if (_peek(-1) == '^') { @@ -890,7 +898,12 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { char32_t ch = _peek(); if (ch == 0x200E || ch == 0x200F || (ch >= 0x202A && ch <= 0x202E) || (ch >= 0x2066 && ch <= 0x2069)) { - Token error = make_error("Invisible text direction control character present in the string, escape it (\"\\u" + String::num_int64(ch, 16) + "\") to avoid confusion."); + Token error; + if (is_raw) { + error = make_error("Invisible text direction control character present in the string, use regular string literal instead of r-string."); + } else { + error = make_error("Invisible text direction control character present in the string, escape it (\"\\u" + String::num_int64(ch, 16) + "\") to avoid confusion."); + } error.start_column = column; error.leftmost_column = error.start_column; error.end_column = column + 1; @@ -905,144 +918,164 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() { return make_error("Unterminated string."); } - // Grab escape character. - char32_t code = _peek(); - _advance(); - if (_is_at_end()) { - return make_error("Unterminated string."); - } + if (is_raw) { + if (_peek() == quote_char) { + _advance(); + if (_is_at_end()) { + return make_error("Unterminated string."); + } + result += '\\'; + result += quote_char; + } else if (_peek() == '\\') { // For `\\\"`. + _advance(); + if (_is_at_end()) { + return make_error("Unterminated string."); + } + result += '\\'; + result += '\\'; + } else { + result += '\\'; + } + } else { + // Grab escape character. + char32_t code = _peek(); + _advance(); + if (_is_at_end()) { + return make_error("Unterminated string."); + } - char32_t escaped = 0; - bool valid_escape = true; + char32_t escaped = 0; + bool valid_escape = true; - switch (code) { - case 'a': - escaped = '\a'; - break; - case 'b': - escaped = '\b'; - break; - case 'f': - escaped = '\f'; - break; - case 'n': - escaped = '\n'; - break; - case 'r': - escaped = '\r'; - break; - case 't': - escaped = '\t'; - break; - case 'v': - escaped = '\v'; - break; - case '\'': - escaped = '\''; - break; - case '\"': - escaped = '\"'; - break; - case '\\': - escaped = '\\'; - break; - case 'U': - case 'u': { - // Hexadecimal sequence. - int hex_len = (code == 'U') ? 6 : 4; - for (int j = 0; j < hex_len; j++) { - if (_is_at_end()) { - return make_error("Unterminated string."); + switch (code) { + case 'a': + escaped = '\a'; + break; + case 'b': + escaped = '\b'; + break; + case 'f': + escaped = '\f'; + break; + case 'n': + escaped = '\n'; + break; + case 'r': + escaped = '\r'; + break; + case 't': + escaped = '\t'; + break; + case 'v': + escaped = '\v'; + break; + case '\'': + escaped = '\''; + break; + case '\"': + escaped = '\"'; + break; + case '\\': + escaped = '\\'; + break; + case 'U': + case 'u': { + // Hexadecimal sequence. + int hex_len = (code == 'U') ? 6 : 4; + for (int j = 0; j < hex_len; j++) { + if (_is_at_end()) { + return make_error("Unterminated string."); + } + + char32_t digit = _peek(); + char32_t value = 0; + if (is_digit(digit)) { + value = digit - '0'; + } else if (digit >= 'a' && digit <= 'f') { + value = digit - 'a'; + value += 10; + } else if (digit >= 'A' && digit <= 'F') { + value = digit - 'A'; + value += 10; + } else { + // Make error, but keep parsing the string. + Token error = make_error("Invalid hexadecimal digit in unicode escape sequence."); + error.start_column = column; + error.leftmost_column = error.start_column; + error.end_column = column + 1; + error.rightmost_column = error.end_column; + push_error(error); + valid_escape = false; + break; + } + + escaped <<= 4; + escaped |= value; + + _advance(); } - - char32_t digit = _peek(); - char32_t value = 0; - if (is_digit(digit)) { - value = digit - '0'; - } else if (digit >= 'a' && digit <= 'f') { - value = digit - 'a'; - value += 10; - } else if (digit >= 'A' && digit <= 'F') { - value = digit - 'A'; - value += 10; - } else { - // Make error, but keep parsing the string. - Token error = make_error("Invalid hexadecimal digit in unicode escape sequence."); - error.start_column = column; - error.leftmost_column = error.start_column; - error.end_column = column + 1; - error.rightmost_column = error.end_column; - push_error(error); - valid_escape = false; + } break; + case '\r': + if (_peek() != '\n') { + // Carriage return without newline in string. (???) + // Just add it to the string and keep going. + result += ch; + _advance(); break; } - - escaped <<= 4; - escaped |= value; - - _advance(); - } - } break; - case '\r': - if (_peek() != '\n') { - // Carriage return without newline in string. (???) - // Just add it to the string and keep going. - result += ch; - _advance(); + [[fallthrough]]; + case '\n': + // Escaping newline. + newline(false); + valid_escape = false; // Don't add to the string. break; - } - [[fallthrough]]; - case '\n': - // Escaping newline. - newline(false); - valid_escape = false; // Don't add to the string. - break; - default: - Token error = make_error("Invalid escape in string."); - error.start_column = column - 2; - error.leftmost_column = error.start_column; - push_error(error); - valid_escape = false; - break; - } - // Parse UTF-16 pair. - if (valid_escape) { - if ((escaped & 0xfffffc00) == 0xd800) { - if (prev == 0) { - prev = escaped; - prev_pos = column - 2; - continue; - } else { - Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); + default: + Token error = make_error("Invalid escape in string."); error.start_column = column - 2; error.leftmost_column = error.start_column; push_error(error); valid_escape = false; - prev = 0; + break; + } + // Parse UTF-16 pair. + if (valid_escape) { + if ((escaped & 0xfffffc00) == 0xd800) { + if (prev == 0) { + prev = escaped; + prev_pos = column - 2; + continue; + } else { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate."); + error.start_column = column - 2; + error.leftmost_column = error.start_column; + push_error(error); + valid_escape = false; + prev = 0; + } + } else if ((escaped & 0xfffffc00) == 0xdc00) { + if (prev == 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired trail surrogate."); + error.start_column = column - 2; + error.leftmost_column = error.start_column; + push_error(error); + valid_escape = false; + } else { + escaped = (prev << 10UL) + escaped - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + prev = 0; + } } - } else if ((escaped & 0xfffffc00) == 0xdc00) { - if (prev == 0) { - Token error = make_error("Invalid UTF-16 sequence in string, unpaired trail surrogate"); - error.start_column = column - 2; + if (prev != 0) { + Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate."); + error.start_column = prev_pos; error.leftmost_column = error.start_column; push_error(error); - valid_escape = false; - } else { - escaped = (prev << 10UL) + escaped - ((0xd800 << 10UL) + 0xdc00 - 0x10000); prev = 0; } } - if (prev != 0) { - Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate"); - error.start_column = prev_pos; - error.leftmost_column = error.start_column; - push_error(error); - prev = 0; - } - } - if (valid_escape) { - result += escaped; + if (valid_escape) { + result += escaped; + } } } else if (ch == quote_char) { if (prev != 0) { @@ -1216,7 +1249,7 @@ void GDScriptTokenizer::check_indent() { if (line_continuation || multiline_mode) { // We cleared up all the whitespace at the beginning of the line. - // But if this is a continuation or multiline mode and we don't want any indentation change. + // If this is a line continuation or we're in multiline mode then we don't want any indentation changes. return; } @@ -1416,6 +1449,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { if (is_digit(c)) { return number(); + } else if (c == 'r' && (_peek() == '"' || _peek() == '\'')) { + // Raw string literals. + return string(); } else if (is_unicode_identifier_start(c)) { return potential_identifier(); } diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index 068393cee9..6dd8a98652 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -105,6 +105,7 @@ public: PASS, RETURN, MATCH, + WHEN, // Keywords AS, ASSERT, @@ -187,6 +188,8 @@ public: #ifdef TOOLS_ENABLED struct CommentData { String comment; + // true: Comment starts at beginning of line or after indentation. + // false: Inline comment (starts after some code). bool new_line = false; CommentData() {} CommentData(const String &p_comment, bool p_new_line) { diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index 030950267d..40c564c36b 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -45,14 +45,12 @@ #define VALIDATE_ARG_COUNT(m_count) \ if (p_arg_count < m_count) { \ r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; \ - r_error.argument = m_count; \ r_error.expected = m_count; \ *r_ret = Variant(); \ return; \ } \ if (p_arg_count > m_count) { \ r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; \ - r_error.argument = m_count; \ r_error.expected = m_count; \ *r_ret = Variant(); \ return; \ @@ -85,6 +83,7 @@ #endif struct GDScriptUtilityFunctionsDefinitions { +#ifndef DISABLE_DEPRECATED static inline void convert(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { VALIDATE_ARG_COUNT(2); VALIDATE_ARG_INT(1); @@ -98,8 +97,12 @@ struct GDScriptUtilityFunctionsDefinitions { } else { Variant::construct(Variant::Type(type), *r_ret, p_args, 1, r_error); + if (r_error.error != Callable::CallError::CALL_OK) { + *r_ret = vformat(RTR(R"(Cannot convert "%s" to "%s".)"), Variant::get_type_name(p_args[0]->get_type()), Variant::get_type_name(Variant::Type(type))); + } } } +#endif // DISABLE_DEPRECATED static inline void type_exists(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { VALIDATE_ARG_COUNT(1); @@ -117,7 +120,6 @@ struct GDScriptUtilityFunctionsDefinitions { switch (p_arg_count) { case 0: { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 1; r_error.expected = 1; *r_ret = Variant(); } break; @@ -131,8 +133,8 @@ struct GDScriptUtilityFunctionsDefinitions { } Error err = arr.resize(count); if (err != OK) { + *r_ret = RTR("Cannot resize array."); r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - *r_ret = Variant(); return; } @@ -156,8 +158,8 @@ struct GDScriptUtilityFunctionsDefinitions { } Error err = arr.resize(to - from); if (err != OK) { + *r_ret = RTR("Cannot resize array."); r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - *r_ret = Variant(); return; } for (int i = from; i < to; i++) { @@ -200,8 +202,8 @@ struct GDScriptUtilityFunctionsDefinitions { Error err = arr.resize(count); if (err != OK) { + *r_ret = RTR("Cannot resize array."); r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - *r_ret = Variant(); return; } @@ -221,7 +223,6 @@ struct GDScriptUtilityFunctionsDefinitions { } break; default: { r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = 3; r_error.expected = 3; *r_ret = Variant(); @@ -249,6 +250,7 @@ struct GDScriptUtilityFunctionsDefinitions { } else if (p_args[0]->get_type() != Variant::OBJECT) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; + r_error.expected = Variant::OBJECT; *r_ret = Variant(); } else { Object *obj = *p_args[0]; @@ -277,7 +279,7 @@ struct GDScriptUtilityFunctionsDefinitions { Vector<StringName> sname; while (p->_owner) { - sname.push_back(p->name); + sname.push_back(p->local_name); p = p->_owner; } sname.reverse(); @@ -371,7 +373,7 @@ struct GDScriptUtilityFunctionsDefinitions { *r_ret = gdscr->_new(nullptr, -1 /*skip initializer*/, r_error); if (r_error.error != Callable::CallError::CALL_OK) { - *r_ret = Variant(); + *r_ret = RTR("Cannot instantiate GDScript class."); return; } @@ -388,13 +390,13 @@ struct GDScriptUtilityFunctionsDefinitions { static inline void Color8(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { if (p_arg_count < 3) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_error.argument = 3; + r_error.expected = 3; *r_ret = Variant(); return; } if (p_arg_count > 4) { r_error.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_error.argument = 4; + r_error.expected = 4; *r_ret = Variant(); return; } @@ -703,7 +705,9 @@ static void _register_function(const String &p_name, const MethodInfo &p_method_ PropertyInfo(Variant::NIL, m_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT) void GDScriptUtilityFunctions::register_functions() { +#ifndef DISABLE_DEPRECATED REGISTER_VARIANT_FUNC(convert, true, VARARG("what"), ARG("type", Variant::INT)); +#endif // DISABLE_DEPRECATED REGISTER_FUNC(type_exists, true, Variant::BOOL, ARG("type", Variant::STRING_NAME)); REGISTER_FUNC(_char, true, Variant::STRING, ARG("char", Variant::INT)); REGISTER_VARARG_FUNC(range, false, Variant::ARRAY); @@ -725,50 +729,50 @@ void GDScriptUtilityFunctions::unregister_functions() { GDScriptUtilityFunctions::FunctionPtr GDScriptUtilityFunctions::get_function(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, nullptr); + ERR_FAIL_NULL_V(info, nullptr); return info->function; } bool GDScriptUtilityFunctions::has_function_return_value(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, false); + ERR_FAIL_NULL_V(info, false); return info->info.return_val.type != Variant::NIL || bool(info->info.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT); } Variant::Type GDScriptUtilityFunctions::get_function_return_type(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, Variant::NIL); + ERR_FAIL_NULL_V(info, Variant::NIL); return info->info.return_val.type; } StringName GDScriptUtilityFunctions::get_function_return_class(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, StringName()); + ERR_FAIL_NULL_V(info, StringName()); return info->info.return_val.class_name; } Variant::Type GDScriptUtilityFunctions::get_function_argument_type(const StringName &p_function, int p_arg) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, Variant::NIL); + ERR_FAIL_NULL_V(info, Variant::NIL); ERR_FAIL_COND_V(p_arg >= info->info.arguments.size(), Variant::NIL); return info->info.arguments[p_arg].type; } int GDScriptUtilityFunctions::get_function_argument_count(const StringName &p_function, int p_arg) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, 0); + ERR_FAIL_NULL_V(info, 0); return info->info.arguments.size(); } bool GDScriptUtilityFunctions::is_function_vararg(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, false); + ERR_FAIL_NULL_V(info, false); return (bool)(info->info.flags & METHOD_FLAG_VARARG); } bool GDScriptUtilityFunctions::is_function_constant(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, false); + ERR_FAIL_NULL_V(info, false); return info->is_constant; } @@ -784,6 +788,6 @@ void GDScriptUtilityFunctions::get_function_list(List<StringName> *r_functions) MethodInfo GDScriptUtilityFunctions::get_function_info(const StringName &p_function) { GDScriptUtilityFunctionInfo *info = utility_function_table.lookup_ptr(p_function); - ERR_FAIL_COND_V(!info, MethodInfo()); + ERR_FAIL_NULL_V(info, MethodInfo()); return info->info; } diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 1ddd54b323..d31411b26b 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -116,6 +116,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { int errorarg = p_err.argument; + ERR_FAIL_COND_V_MSG(errorarg < 0 || argptrs[errorarg] == nullptr, "GDScript bug (please report): Invalid CallError argument index or null pointer.", "Invalid CallError argument index or null pointer."); // Handle the Object to Object case separately as we don't have further class details. #ifdef DEBUG_ENABLED if (p_err.expected == Variant::OBJECT && argptrs[errorarg]->get_type() == p_err.expected) { @@ -128,9 +129,9 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const err_text = "Invalid type in " + p_where + ". Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + "."; } } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) { - err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments."; + err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments."; } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) { - err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.argument) + " arguments."; + err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments."; } else if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { err_text = "Invalid call. Nonexistent " + p_where + "."; } else if (p_err.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) { @@ -186,191 +187,155 @@ void (*type_init_function_table[])(Variant *) = { }; #if defined(__GNUC__) -#define OPCODES_TABLE \ - static const void *switch_table_ops[] = { \ - &&OPCODE_OPERATOR, \ - &&OPCODE_OPERATOR_VALIDATED, \ - &&OPCODE_TYPE_TEST_BUILTIN, \ - &&OPCODE_TYPE_TEST_ARRAY, \ - &&OPCODE_TYPE_TEST_NATIVE, \ - &&OPCODE_TYPE_TEST_SCRIPT, \ - &&OPCODE_SET_KEYED, \ - &&OPCODE_SET_KEYED_VALIDATED, \ - &&OPCODE_SET_INDEXED_VALIDATED, \ - &&OPCODE_GET_KEYED, \ - &&OPCODE_GET_KEYED_VALIDATED, \ - &&OPCODE_GET_INDEXED_VALIDATED, \ - &&OPCODE_SET_NAMED, \ - &&OPCODE_SET_NAMED_VALIDATED, \ - &&OPCODE_GET_NAMED, \ - &&OPCODE_GET_NAMED_VALIDATED, \ - &&OPCODE_SET_MEMBER, \ - &&OPCODE_GET_MEMBER, \ - &&OPCODE_SET_STATIC_VARIABLE, \ - &&OPCODE_GET_STATIC_VARIABLE, \ - &&OPCODE_ASSIGN, \ - &&OPCODE_ASSIGN_TRUE, \ - &&OPCODE_ASSIGN_FALSE, \ - &&OPCODE_ASSIGN_TYPED_BUILTIN, \ - &&OPCODE_ASSIGN_TYPED_ARRAY, \ - &&OPCODE_ASSIGN_TYPED_NATIVE, \ - &&OPCODE_ASSIGN_TYPED_SCRIPT, \ - &&OPCODE_CAST_TO_BUILTIN, \ - &&OPCODE_CAST_TO_NATIVE, \ - &&OPCODE_CAST_TO_SCRIPT, \ - &&OPCODE_CONSTRUCT, \ - &&OPCODE_CONSTRUCT_VALIDATED, \ - &&OPCODE_CONSTRUCT_ARRAY, \ - &&OPCODE_CONSTRUCT_TYPED_ARRAY, \ - &&OPCODE_CONSTRUCT_DICTIONARY, \ - &&OPCODE_CALL, \ - &&OPCODE_CALL_RETURN, \ - &&OPCODE_CALL_ASYNC, \ - &&OPCODE_CALL_UTILITY, \ - &&OPCODE_CALL_UTILITY_VALIDATED, \ - &&OPCODE_CALL_GDSCRIPT_UTILITY, \ - &&OPCODE_CALL_BUILTIN_TYPE_VALIDATED, \ - &&OPCODE_CALL_SELF_BASE, \ - &&OPCODE_CALL_METHOD_BIND, \ - &&OPCODE_CALL_METHOD_BIND_RET, \ - &&OPCODE_CALL_BUILTIN_STATIC, \ - &&OPCODE_CALL_NATIVE_STATIC, \ - &&OPCODE_CALL_PTRCALL_NO_RETURN, \ - &&OPCODE_CALL_PTRCALL_BOOL, \ - &&OPCODE_CALL_PTRCALL_INT, \ - &&OPCODE_CALL_PTRCALL_FLOAT, \ - &&OPCODE_CALL_PTRCALL_STRING, \ - &&OPCODE_CALL_PTRCALL_VECTOR2, \ - &&OPCODE_CALL_PTRCALL_VECTOR2I, \ - &&OPCODE_CALL_PTRCALL_RECT2, \ - &&OPCODE_CALL_PTRCALL_RECT2I, \ - &&OPCODE_CALL_PTRCALL_VECTOR3, \ - &&OPCODE_CALL_PTRCALL_VECTOR3I, \ - &&OPCODE_CALL_PTRCALL_TRANSFORM2D, \ - &&OPCODE_CALL_PTRCALL_VECTOR4, \ - &&OPCODE_CALL_PTRCALL_VECTOR4I, \ - &&OPCODE_CALL_PTRCALL_PLANE, \ - &&OPCODE_CALL_PTRCALL_QUATERNION, \ - &&OPCODE_CALL_PTRCALL_AABB, \ - &&OPCODE_CALL_PTRCALL_BASIS, \ - &&OPCODE_CALL_PTRCALL_TRANSFORM3D, \ - &&OPCODE_CALL_PTRCALL_PROJECTION, \ - &&OPCODE_CALL_PTRCALL_COLOR, \ - &&OPCODE_CALL_PTRCALL_STRING_NAME, \ - &&OPCODE_CALL_PTRCALL_NODE_PATH, \ - &&OPCODE_CALL_PTRCALL_RID, \ - &&OPCODE_CALL_PTRCALL_OBJECT, \ - &&OPCODE_CALL_PTRCALL_CALLABLE, \ - &&OPCODE_CALL_PTRCALL_SIGNAL, \ - &&OPCODE_CALL_PTRCALL_DICTIONARY, \ - &&OPCODE_CALL_PTRCALL_ARRAY, \ - &&OPCODE_CALL_PTRCALL_PACKED_BYTE_ARRAY, \ - &&OPCODE_CALL_PTRCALL_PACKED_INT32_ARRAY, \ - &&OPCODE_CALL_PTRCALL_PACKED_INT64_ARRAY, \ - &&OPCODE_CALL_PTRCALL_PACKED_FLOAT32_ARRAY, \ - &&OPCODE_CALL_PTRCALL_PACKED_FLOAT64_ARRAY, \ - &&OPCODE_CALL_PTRCALL_PACKED_STRING_ARRAY, \ - &&OPCODE_CALL_PTRCALL_PACKED_VECTOR2_ARRAY, \ - &&OPCODE_CALL_PTRCALL_PACKED_VECTOR3_ARRAY, \ - &&OPCODE_CALL_PTRCALL_PACKED_COLOR_ARRAY, \ - &&OPCODE_AWAIT, \ - &&OPCODE_AWAIT_RESUME, \ - &&OPCODE_CREATE_LAMBDA, \ - &&OPCODE_CREATE_SELF_LAMBDA, \ - &&OPCODE_JUMP, \ - &&OPCODE_JUMP_IF, \ - &&OPCODE_JUMP_IF_NOT, \ - &&OPCODE_JUMP_TO_DEF_ARGUMENT, \ - &&OPCODE_JUMP_IF_SHARED, \ - &&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, \ - &&OPCODE_ITERATE_BEGIN_VECTOR2, \ - &&OPCODE_ITERATE_BEGIN_VECTOR2I, \ - &&OPCODE_ITERATE_BEGIN_VECTOR3, \ - &&OPCODE_ITERATE_BEGIN_VECTOR3I, \ - &&OPCODE_ITERATE_BEGIN_STRING, \ - &&OPCODE_ITERATE_BEGIN_DICTIONARY, \ - &&OPCODE_ITERATE_BEGIN_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_BYTE_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_INT32_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_INT64_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT32_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT64_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_STRING_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR2_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR3_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_PACKED_COLOR_ARRAY, \ - &&OPCODE_ITERATE_BEGIN_OBJECT, \ - &&OPCODE_ITERATE, \ - &&OPCODE_ITERATE_INT, \ - &&OPCODE_ITERATE_FLOAT, \ - &&OPCODE_ITERATE_VECTOR2, \ - &&OPCODE_ITERATE_VECTOR2I, \ - &&OPCODE_ITERATE_VECTOR3, \ - &&OPCODE_ITERATE_VECTOR3I, \ - &&OPCODE_ITERATE_STRING, \ - &&OPCODE_ITERATE_DICTIONARY, \ - &&OPCODE_ITERATE_ARRAY, \ - &&OPCODE_ITERATE_PACKED_BYTE_ARRAY, \ - &&OPCODE_ITERATE_PACKED_INT32_ARRAY, \ - &&OPCODE_ITERATE_PACKED_INT64_ARRAY, \ - &&OPCODE_ITERATE_PACKED_FLOAT32_ARRAY, \ - &&OPCODE_ITERATE_PACKED_FLOAT64_ARRAY, \ - &&OPCODE_ITERATE_PACKED_STRING_ARRAY, \ - &&OPCODE_ITERATE_PACKED_VECTOR2_ARRAY, \ - &&OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, \ - &&OPCODE_ITERATE_PACKED_COLOR_ARRAY, \ - &&OPCODE_ITERATE_OBJECT, \ - &&OPCODE_STORE_GLOBAL, \ - &&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_VECTOR4, \ - &&OPCODE_TYPE_ADJUST_VECTOR4I, \ - &&OPCODE_TYPE_ADJUST_PLANE, \ - &&OPCODE_TYPE_ADJUST_QUATERNION, \ - &&OPCODE_TYPE_ADJUST_AABB, \ - &&OPCODE_TYPE_ADJUST_BASIS, \ - &&OPCODE_TYPE_ADJUST_TRANSFORM3D, \ - &&OPCODE_TYPE_ADJUST_PROJECTION, \ - &&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, \ - &&OPCODE_END \ - }; \ +#define OPCODES_TABLE \ + static const void *switch_table_ops[] = { \ + &&OPCODE_OPERATOR, \ + &&OPCODE_OPERATOR_VALIDATED, \ + &&OPCODE_TYPE_TEST_BUILTIN, \ + &&OPCODE_TYPE_TEST_ARRAY, \ + &&OPCODE_TYPE_TEST_NATIVE, \ + &&OPCODE_TYPE_TEST_SCRIPT, \ + &&OPCODE_SET_KEYED, \ + &&OPCODE_SET_KEYED_VALIDATED, \ + &&OPCODE_SET_INDEXED_VALIDATED, \ + &&OPCODE_GET_KEYED, \ + &&OPCODE_GET_KEYED_VALIDATED, \ + &&OPCODE_GET_INDEXED_VALIDATED, \ + &&OPCODE_SET_NAMED, \ + &&OPCODE_SET_NAMED_VALIDATED, \ + &&OPCODE_GET_NAMED, \ + &&OPCODE_GET_NAMED_VALIDATED, \ + &&OPCODE_SET_MEMBER, \ + &&OPCODE_GET_MEMBER, \ + &&OPCODE_SET_STATIC_VARIABLE, \ + &&OPCODE_GET_STATIC_VARIABLE, \ + &&OPCODE_ASSIGN, \ + &&OPCODE_ASSIGN_TRUE, \ + &&OPCODE_ASSIGN_FALSE, \ + &&OPCODE_ASSIGN_TYPED_BUILTIN, \ + &&OPCODE_ASSIGN_TYPED_ARRAY, \ + &&OPCODE_ASSIGN_TYPED_NATIVE, \ + &&OPCODE_ASSIGN_TYPED_SCRIPT, \ + &&OPCODE_CAST_TO_BUILTIN, \ + &&OPCODE_CAST_TO_NATIVE, \ + &&OPCODE_CAST_TO_SCRIPT, \ + &&OPCODE_CONSTRUCT, \ + &&OPCODE_CONSTRUCT_VALIDATED, \ + &&OPCODE_CONSTRUCT_ARRAY, \ + &&OPCODE_CONSTRUCT_TYPED_ARRAY, \ + &&OPCODE_CONSTRUCT_DICTIONARY, \ + &&OPCODE_CALL, \ + &&OPCODE_CALL_RETURN, \ + &&OPCODE_CALL_ASYNC, \ + &&OPCODE_CALL_UTILITY, \ + &&OPCODE_CALL_UTILITY_VALIDATED, \ + &&OPCODE_CALL_GDSCRIPT_UTILITY, \ + &&OPCODE_CALL_BUILTIN_TYPE_VALIDATED, \ + &&OPCODE_CALL_SELF_BASE, \ + &&OPCODE_CALL_METHOD_BIND, \ + &&OPCODE_CALL_METHOD_BIND_RET, \ + &&OPCODE_CALL_BUILTIN_STATIC, \ + &&OPCODE_CALL_NATIVE_STATIC, \ + &&OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN, \ + &&OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN, \ + &&OPCODE_AWAIT, \ + &&OPCODE_AWAIT_RESUME, \ + &&OPCODE_CREATE_LAMBDA, \ + &&OPCODE_CREATE_SELF_LAMBDA, \ + &&OPCODE_JUMP, \ + &&OPCODE_JUMP_IF, \ + &&OPCODE_JUMP_IF_NOT, \ + &&OPCODE_JUMP_TO_DEF_ARGUMENT, \ + &&OPCODE_JUMP_IF_SHARED, \ + &&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, \ + &&OPCODE_ITERATE_BEGIN_VECTOR2, \ + &&OPCODE_ITERATE_BEGIN_VECTOR2I, \ + &&OPCODE_ITERATE_BEGIN_VECTOR3, \ + &&OPCODE_ITERATE_BEGIN_VECTOR3I, \ + &&OPCODE_ITERATE_BEGIN_STRING, \ + &&OPCODE_ITERATE_BEGIN_DICTIONARY, \ + &&OPCODE_ITERATE_BEGIN_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_BYTE_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_INT32_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_INT64_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT32_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_FLOAT64_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_STRING_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR2_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_VECTOR3_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_PACKED_COLOR_ARRAY, \ + &&OPCODE_ITERATE_BEGIN_OBJECT, \ + &&OPCODE_ITERATE, \ + &&OPCODE_ITERATE_INT, \ + &&OPCODE_ITERATE_FLOAT, \ + &&OPCODE_ITERATE_VECTOR2, \ + &&OPCODE_ITERATE_VECTOR2I, \ + &&OPCODE_ITERATE_VECTOR3, \ + &&OPCODE_ITERATE_VECTOR3I, \ + &&OPCODE_ITERATE_STRING, \ + &&OPCODE_ITERATE_DICTIONARY, \ + &&OPCODE_ITERATE_ARRAY, \ + &&OPCODE_ITERATE_PACKED_BYTE_ARRAY, \ + &&OPCODE_ITERATE_PACKED_INT32_ARRAY, \ + &&OPCODE_ITERATE_PACKED_INT64_ARRAY, \ + &&OPCODE_ITERATE_PACKED_FLOAT32_ARRAY, \ + &&OPCODE_ITERATE_PACKED_FLOAT64_ARRAY, \ + &&OPCODE_ITERATE_PACKED_STRING_ARRAY, \ + &&OPCODE_ITERATE_PACKED_VECTOR2_ARRAY, \ + &&OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, \ + &&OPCODE_ITERATE_PACKED_COLOR_ARRAY, \ + &&OPCODE_ITERATE_OBJECT, \ + &&OPCODE_STORE_GLOBAL, \ + &&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_VECTOR4, \ + &&OPCODE_TYPE_ADJUST_VECTOR4I, \ + &&OPCODE_TYPE_ADJUST_PLANE, \ + &&OPCODE_TYPE_ADJUST_QUATERNION, \ + &&OPCODE_TYPE_ADJUST_AABB, \ + &&OPCODE_TYPE_ADJUST_BASIS, \ + &&OPCODE_TYPE_ADJUST_TRANSFORM3D, \ + &&OPCODE_TYPE_ADJUST_PROJECTION, \ + &&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, \ + &&OPCODE_END \ + }; \ static_assert((sizeof(switch_table_ops) / sizeof(switch_table_ops[0]) == (OPCODE_END + 1)), "Opcodes in jump table aren't the same as opcodes in enum."); #define OPCODE(m_op) \ @@ -397,7 +362,13 @@ void (*type_init_function_table[])(Variant *) = { #define OPCODES_END #define OPCODES_OUT #define DISPATCH_OPCODE continue +#ifdef _MSC_VER +#define OPCODE_SWITCH(m_test) \ + __assume(m_test <= OPCODE_END); \ + switch (m_test) +#else #define OPCODE_SWITCH(m_test) switch (m_test) +#endif #define OPCODE_BREAK break #define OPCODE_OUT break #endif @@ -466,8 +437,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a err_file = "<built-in>"; } String err_func = name; - if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && !p_instance->script->name.is_empty()) { - err_func = p_instance->script->name + "." + err_func; + if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && p_instance->script->local_name != StringName()) { + err_func = p_instance->script->local_name.operator String() + "." + err_func; } int err_line = _initial_line; const char *err_text = "Stack overflow. Check for infinite recursion in your script."; @@ -482,7 +453,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a Variant retvalue; Variant *stack = nullptr; Variant **instruction_args = nullptr; - const void **call_args_ptr = nullptr; int defarg = 0; #ifdef DEBUG_ENABLED @@ -511,13 +481,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (p_argcount != _argument_count) { if (p_argcount > _argument_count) { r_err.error = Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS; - r_err.argument = _argument_count; - + r_err.expected = _argument_count; call_depth--; return _get_default_variant_for_data_type(return_type); } else if (p_argcount < _argument_count - _default_arg_count) { r_err.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; - r_err.argument = _argument_count - _default_arg_count; + r_err.expected = _argument_count - _default_arg_count; call_depth--; return _get_default_variant_for_data_type(return_type); } else { @@ -572,12 +541,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } } - if (_ptrcall_args_size) { - call_args_ptr = (const void **)alloca(_ptrcall_args_size * sizeof(void *)); - } else { - call_args_ptr = nullptr; - } - if (p_instance) { memnew_placement(&stack[ADDR_STACK_SELF], Variant(p_instance->owner)); script = p_instance->script.ptr(); @@ -699,6 +662,14 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a uint32_t op_signature = _code_ptr[ip + 5]; uint32_t actual_signature = (a->get_type() << 8) | (b->get_type()); +#ifdef DEBUG_ENABLED + if (op == Variant::OP_DIVIDE || op == Variant::OP_MODULE) { + // Don't optimize division and modulo since there's not check for division by zero with validated calls. + op_signature = 0xFFFF; + _code_ptr[ip + 5] = op_signature; + } +#endif + // Check if this is the first run. If so, store the current signature for the optimized path. if (unlikely(op_signature == 0)) { static Mutex initializer_mutex; @@ -1948,106 +1919,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; -#ifdef DEBUG_ENABLED -#define OPCODE_CALL_PTR(m_type) \ - OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \ - LOAD_INSTRUCTION_ARGS \ - CHECK_SPACE(3 + instr_arg_count); \ - ip += instr_arg_count; \ - int argc = _code_ptr[ip + 1]; \ - GD_ERR_BREAK(argc < 0); \ - GET_INSTRUCTION_ARG(base, argc); \ - GD_ERR_BREAK(_code_ptr[ip + 2] < 0 || _code_ptr[ip + 2] >= _methods_count); \ - MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; \ - bool freed = false; \ - Object *base_obj = base->get_validated_object_with_check(freed); \ - if (freed) { \ - err_text = METHOD_CALL_ON_FREED_INSTANCE_ERROR(method); \ - OPCODE_BREAK; \ - } else if (!base_obj) { \ - err_text = METHOD_CALL_ON_NULL_VALUE_ERROR(method); \ - OPCODE_BREAK; \ - } \ - const void **argptrs = call_args_ptr; \ - for (int i = 0; i < argc; i++) { \ - GET_INSTRUCTION_ARG(v, i); \ - argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); \ - } \ - uint64_t call_time = 0; \ - if (GDScriptLanguage::get_singleton()->profiling) { \ - call_time = OS::get_singleton()->get_ticks_usec(); \ - } \ - GET_INSTRUCTION_ARG(ret, argc + 1); \ - VariantInternal::initialize(ret, Variant::m_type); \ - void *ret_opaque = VariantInternal::OP_GET_##m_type(ret); \ - method->ptrcall(base_obj, argptrs, ret_opaque); \ - if (GDScriptLanguage::get_singleton()->profiling) { \ - function_call_time += OS::get_singleton()->get_ticks_usec() - call_time; \ - } \ - ip += 3; \ - } \ - DISPATCH_OPCODE -#else -#define OPCODE_CALL_PTR(m_type) \ - OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \ - LOAD_INSTRUCTION_ARGS \ - CHECK_SPACE(3 + instr_arg_count); \ - ip += instr_arg_count; \ - int argc = _code_ptr[ip + 1]; \ - GET_INSTRUCTION_ARG(base, argc); \ - MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; \ - Object *base_obj = *VariantInternal::get_object(base); \ - const void **argptrs = call_args_ptr; \ - for (int i = 0; i < argc; i++) { \ - GET_INSTRUCTION_ARG(v, i); \ - argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); \ - } \ - GET_INSTRUCTION_ARG(ret, argc + 1); \ - VariantInternal::initialize(ret, Variant::m_type); \ - void *ret_opaque = VariantInternal::OP_GET_##m_type(ret); \ - method->ptrcall(base_obj, argptrs, ret_opaque); \ - ip += 3; \ - } \ - DISPATCH_OPCODE -#endif - - OPCODE_CALL_PTR(BOOL); - OPCODE_CALL_PTR(INT); - OPCODE_CALL_PTR(FLOAT); - OPCODE_CALL_PTR(STRING); - OPCODE_CALL_PTR(VECTOR2); - OPCODE_CALL_PTR(VECTOR2I); - OPCODE_CALL_PTR(RECT2); - OPCODE_CALL_PTR(RECT2I); - OPCODE_CALL_PTR(VECTOR3); - OPCODE_CALL_PTR(VECTOR3I); - OPCODE_CALL_PTR(TRANSFORM2D); - OPCODE_CALL_PTR(VECTOR4); - OPCODE_CALL_PTR(VECTOR4I); - OPCODE_CALL_PTR(PLANE); - OPCODE_CALL_PTR(QUATERNION); - OPCODE_CALL_PTR(AABB); - OPCODE_CALL_PTR(BASIS); - OPCODE_CALL_PTR(TRANSFORM3D); - OPCODE_CALL_PTR(PROJECTION); - OPCODE_CALL_PTR(COLOR); - OPCODE_CALL_PTR(STRING_NAME); - OPCODE_CALL_PTR(NODE_PATH); - OPCODE_CALL_PTR(RID); - OPCODE_CALL_PTR(CALLABLE); - OPCODE_CALL_PTR(SIGNAL); - OPCODE_CALL_PTR(DICTIONARY); - OPCODE_CALL_PTR(ARRAY); - OPCODE_CALL_PTR(PACKED_BYTE_ARRAY); - OPCODE_CALL_PTR(PACKED_INT32_ARRAY); - OPCODE_CALL_PTR(PACKED_INT64_ARRAY); - OPCODE_CALL_PTR(PACKED_FLOAT32_ARRAY); - OPCODE_CALL_PTR(PACKED_FLOAT64_ARRAY); - OPCODE_CALL_PTR(PACKED_STRING_ARRAY); - OPCODE_CALL_PTR(PACKED_VECTOR2_ARRAY); - OPCODE_CALL_PTR(PACKED_VECTOR3_ARRAY); - OPCODE_CALL_PTR(PACKED_COLOR_ARRAY); - OPCODE(OPCODE_CALL_PTRCALL_OBJECT) { + OPCODE(OPCODE_CALL_METHOD_BIND_VALIDATED_RETURN) { LOAD_INSTRUCTION_ARGS CHECK_SPACE(3 + instr_arg_count); @@ -2060,6 +1932,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a MethodBind *method = _methods_ptr[_code_ptr[ip + 2]]; GET_INSTRUCTION_ARG(base, argc); + #ifdef DEBUG_ENABLED bool freed = false; Object *base_obj = base->get_validated_object_with_check(freed); @@ -2074,12 +1947,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a Object *base_obj = *VariantInternal::get_object(base); #endif - const void **argptrs = call_args_ptr; + Variant **argptrs = instruction_args; - for (int i = 0; i < argc; i++) { - GET_INSTRUCTION_ARG(v, i); - argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); - } #ifdef DEBUG_ENABLED uint64_t call_time = 0; @@ -2089,16 +1958,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #endif GET_INSTRUCTION_ARG(ret, argc + 1); - VariantInternal::initialize(ret, Variant::OBJECT); - Object **ret_opaque = VariantInternal::get_object(ret); - method->ptrcall(base_obj, argptrs, ret_opaque); - if (method->is_return_type_raw_object_ptr()) { - // The Variant has to participate in the ref count since the method returns a raw Object *. - VariantInternal::object_assign(ret, *ret_opaque); - } else { - // The method, in case it returns something, returns an already encapsulated object. - VariantInternal::update_object_id(ret); - } + method->validated_call(base_obj, (const Variant **)argptrs, ret); #ifdef DEBUG_ENABLED if (GDScriptLanguage::get_singleton()->profiling) { @@ -2108,7 +1968,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a ip += 3; } DISPATCH_OPCODE; - OPCODE(OPCODE_CALL_PTRCALL_NO_RETURN) { + + OPCODE(OPCODE_CALL_METHOD_BIND_VALIDATED_NO_RETURN) { LOAD_INSTRUCTION_ARGS CHECK_SPACE(3 + instr_arg_count); @@ -2134,12 +1995,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #else Object *base_obj = *VariantInternal::get_object(base); #endif - const void **argptrs = call_args_ptr; - - for (int i = 0; i < argc; i++) { - GET_INSTRUCTION_ARG(v, i); - argptrs[i] = VariantInternal::get_opaque_pointer((const Variant *)v); - } + Variant **argptrs = instruction_args; #ifdef DEBUG_ENABLED uint64_t call_time = 0; @@ -2150,7 +2006,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GET_INSTRUCTION_ARG(ret, argc + 1); VariantInternal::initialize(ret, Variant::NIL); - method->ptrcall(base_obj, argptrs, nullptr); + method->validated_call(base_obj, (const Variant **)argptrs, nullptr); #ifdef DEBUG_ENABLED if (GDScriptLanguage::get_singleton()->profiling) { @@ -2219,11 +2075,11 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED if (err.error != Callable::CallError::CALL_OK) { String methodstr = function; - if (dst->get_type() == Variant::STRING) { + if (dst->get_type() == Variant::STRING && !dst->operator String().is_empty()) { // Call provided error string. - err_text = "Error calling utility function '" + methodstr + "': " + String(*dst); + err_text = vformat(R"*(Error calling utility function "%s()": %s)*", methodstr, *dst); } else { - err_text = _get_call_error(err, "utility function '" + methodstr + "'", (const Variant **)argptrs); + err_text = _get_call_error(err, vformat(R"*(utility function "%s()")*", methodstr), (const Variant **)argptrs); } OPCODE_BREAK; } @@ -2275,13 +2131,12 @@ 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 = "<unknown function>"; - if (dst->get_type() == Variant::STRING) { + String methodstr = gds_utilities_names[_code_ptr[ip + 2]]; + if (dst->get_type() == Variant::STRING && !dst->operator String().is_empty()) { // Call provided error string. - err_text = "Error calling GDScript utility function '" + methodstr + "': " + String(*dst); + err_text = vformat(R"*(Error calling GDScript utility function "%s()": %s)*", methodstr, *dst); } else { - err_text = _get_call_error(err, "GDScript utility function '" + methodstr + "'", (const Variant **)argptrs); + err_text = _get_call_error(err, vformat(R"*(GDScript utility function "%s()")*", methodstr), (const Variant **)argptrs); } OPCODE_BREAK; } @@ -3649,8 +3504,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a err_file = "<built-in>"; } String err_func = name; - if (instance_valid_with_script && !p_instance->script->name.is_empty()) { - err_func = p_instance->script->name + "." + err_func; + if (instance_valid_with_script && p_instance->script->local_name != StringName()) { + err_func = p_instance->script->local_name.operator String() + "." + err_func; } int err_line = line; if (err_text.is_empty()) { diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index 4fec445995..1fe9b0425c 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -88,6 +88,15 @@ String GDScriptWarning::get_message() const { case FUNCTION_USED_AS_PROPERTY: CHECK_SYMBOLS(2); return vformat(R"(The property "%s" was not found in base "%s" but there's a method with the same name. Did you mean to call it?)", symbols[0], symbols[1]); + case UNTYPED_DECLARATION: + CHECK_SYMBOLS(2); + if (symbols[0] == "Function") { + return vformat(R"*(%s "%s()" has no static return type.)*", symbols[0], symbols[1]); + } + return vformat(R"(%s "%s" has no static type.)", symbols[0], symbols[1]); + case INFERRED_DECLARATION: + CHECK_SYMBOLS(2); + return vformat(R"(%s "%s" has an implicitly inferred static type.)", symbols[0], symbols[1]); case UNSAFE_PROPERTY_ACCESS: CHECK_SYMBOLS(2); return vformat(R"(The property "%s" is not present on the inferred type "%s" (but may be present on a subtype).)", symbols[0], symbols[1]); @@ -98,8 +107,8 @@ String GDScriptWarning::get_message() const { CHECK_SYMBOLS(1); return vformat(R"(The value is cast to "%s" but has an unknown type.)", symbols[0]); case UNSAFE_CALL_ARGUMENT: - CHECK_SYMBOLS(4); - return vformat(R"*(The argument %s of the function "%s()" requires a the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3]); + CHECK_SYMBOLS(5); + return vformat(R"*(The argument %s of the %s "%s()" requires the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3], symbols[4]); case UNSAFE_VOID_RETURN: CHECK_SYMBOLS(2); return vformat(R"*(The method "%s()" returns "void" but it's trying to return a call to "%s()" that can't be ensured to also be "void".)*", symbols[0], symbols[1]); @@ -113,14 +122,6 @@ String GDScriptWarning::get_message() const { return R"(The "@static_unload" annotation is redundant because the file does not have a class with static variables.)"; case REDUNDANT_AWAIT: return R"("await" keyword not needed in this case, because the expression isn't a coroutine nor a signal.)"; - case REDUNDANT_FOR_VARIABLE_TYPE: - CHECK_SYMBOLS(3); - if (symbols[1] == symbols[2]) { - return vformat(R"(The for loop iterator "%s" already has inferred type "%s", the specified type is redundant.)", symbols[0], symbols[1]); - } else { - return vformat(R"(The for loop iterator "%s" has inferred type "%s" but its supertype "%s" is specified.)", symbols[0], symbols[1], symbols[2]); - } - break; case ASSERT_ALWAYS_TRUE: return "Assert statement is redundant because the expression is always true."; case ASSERT_ALWAYS_FALSE: @@ -208,6 +209,8 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "PROPERTY_USED_AS_FUNCTION", "CONSTANT_USED_AS_FUNCTION", "FUNCTION_USED_AS_PROPERTY", + "UNTYPED_DECLARATION", + "INFERRED_DECLARATION", "UNSAFE_PROPERTY_ACCESS", "UNSAFE_METHOD_ACCESS", "UNSAFE_CAST", @@ -217,7 +220,6 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "STATIC_CALLED_ON_INSTANCE", "REDUNDANT_STATIC_UNLOAD", "REDUNDANT_AWAIT", - "REDUNDANT_FOR_VARIABLE_TYPE", "ASSERT_ALWAYS_TRUE", "ASSERT_ALWAYS_FALSE", "INTEGER_DIVISION", diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index 73e12eb20e..1aef6fa81b 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -64,6 +64,8 @@ public: PROPERTY_USED_AS_FUNCTION, // Function not found, but there's a property with the same name. CONSTANT_USED_AS_FUNCTION, // Function not found, but there's a constant with the same name. FUNCTION_USED_AS_PROPERTY, // Property not found, but there's a function with the same name. + UNTYPED_DECLARATION, // Variable/parameter/function has no static type, explicitly specified or implicitly inferred. + INFERRED_DECLARATION, // Variable/constant/parameter has an implicitly inferred static type. UNSAFE_PROPERTY_ACCESS, // Property not found in the detected type (but can be in subtypes). UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes). UNSAFE_CAST, // Cast used in an unknown type. @@ -73,7 +75,6 @@ public: STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself. REDUNDANT_STATIC_UNLOAD, // The `@static_unload` annotation is used but the class does not have static data. REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine). - REDUNDANT_FOR_VARIABLE_TYPE, // The for variable type specifier is a supertype of the inferred type. ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true. ASSERT_ALWAYS_FALSE, // Expression for assert argument is always false. INTEGER_DIVISION, // Integer divide by integer, decimal part is discarded. @@ -112,6 +113,8 @@ public: WARN, // PROPERTY_USED_AS_FUNCTION WARN, // CONSTANT_USED_AS_FUNCTION WARN, // FUNCTION_USED_AS_PROPERTY + IGNORE, // UNTYPED_DECLARATION // Static typing is optional, we don't want to spam warnings. + IGNORE, // INFERRED_DECLARATION // Static typing is optional, we don't want to spam warnings. IGNORE, // UNSAFE_PROPERTY_ACCESS // Too common in untyped scenarios. IGNORE, // UNSAFE_METHOD_ACCESS // Too common in untyped scenarios. IGNORE, // UNSAFE_CAST // Too common in untyped scenarios. @@ -121,7 +124,6 @@ public: WARN, // STATIC_CALLED_ON_INSTANCE WARN, // REDUNDANT_STATIC_UNLOAD WARN, // REDUNDANT_AWAIT - WARN, // REDUNDANT_FOR_VARIABLE_TYPE WARN, // ASSERT_ALWAYS_TRUE WARN, // ASSERT_ALWAYS_FALSE WARN, // INTEGER_DIVISION diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 3a5a54e275..36806d2f73 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -32,9 +32,94 @@ #include "../gdscript.h" #include "../gdscript_analyzer.h" +#include "editor/editor_settings.h" #include "gdscript_language_protocol.h" #include "gdscript_workspace.h" +int get_indent_size() { + if (EditorSettings::get_singleton()) { + return EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size"); + } else { + return 4; + } +} + +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) { + return res; + } + // Special case: `line = p_lines.size() + 1` -> root class (range covers everything). + if (this->line >= p_lines.size() + 1) { + res.line = p_lines.size(); + return res; + } + res.line = this->line - 1; + // Note: character outside of `pos_line.length()-1` is valid. + res.character = this->column - 1; + + String pos_line = p_lines[res.line]; + if (pos_line.contains("\t")) { + int tab_size = get_indent_size(); + + int in_col = 1; + int res_char = 0; + + while (res_char < pos_line.size() && in_col < this->column) { + if (pos_line[res_char] == '\t') { + in_col += tab_size; + res_char++; + } else { + in_col++; + res_char++; + } + } + + res.character = res_char; + } + + return res; +} + +GodotPosition GodotPosition::from_lsp(const lsp::Position p_pos, const Vector<String> &p_lines) { + GodotPosition res(p_pos.line + 1, p_pos.character + 1); + + // Line outside of actual text is valid (-> pos/cursor at end of text). + if (res.line > p_lines.size()) { + return res; + } + + String line = p_lines[p_pos.line]; + int tabs_before_char = 0; + for (int i = 0; i < p_pos.character && i < line.length(); i++) { + if (line[i] == '\t') { + tabs_before_char++; + } + } + + if (tabs_before_char > 0) { + int tab_size = get_indent_size(); + res.column += tabs_before_char * (tab_size - 1); + } + + return res; +} + +lsp::Range GodotRange::to_lsp(const Vector<String> &p_lines) const { + lsp::Range res; + res.start = start.to_lsp(p_lines); + res.end = end.to_lsp(p_lines); + return res; +} + +GodotRange GodotRange::from_lsp(const lsp::Range &p_range, const Vector<String> &p_lines) { + GodotPosition start = GodotPosition::from_lsp(p_range.start, p_lines); + GodotPosition end = GodotPosition::from_lsp(p_range.end, p_lines); + return GodotRange(start, end); +} + void ExtendGDScriptParser::update_diagnostics() { diagnostics.clear(); @@ -90,7 +175,7 @@ void ExtendGDScriptParser::update_symbols() { const lsp::DocumentSymbol &symbol = class_symbol.children[i]; members.insert(symbol.name, &symbol); - // cache level one inner classes + // Cache level one inner classes. if (symbol.kind == lsp::SymbolKind::Class) { ClassMembers inner_class; for (int j = 0; j < symbol.children.size(); j++) { @@ -126,10 +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.start.line = LINE_NUMBER_TO_INDEX(token.start_line); - link.range.end.line = LINE_NUMBER_TO_INDEX(token.end_line); - link.range.start.character = LINE_NUMBER_TO_INDEX(token.start_column); - link.range.end.character = LINE_NUMBER_TO_INDEX(token.end_column); + link.range = GodotRange(GodotPosition(token.start_line, token.start_column), GodotPosition(token.end_line, token.end_column)).to_lsp(this->lines); document_links.push_back(link); } } @@ -137,6 +219,12 @@ 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); +} + void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p_class, lsp::DocumentSymbol &r_symbol) { const String uri = get_uri(); @@ -149,13 +237,30 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p } r_symbol.kind = lsp::SymbolKind::Class; r_symbol.deprecated = false; - r_symbol.range.start.line = p_class->start_line; - r_symbol.range.start.character = p_class->start_column; - r_symbol.range.end.line = lines.size(); - r_symbol.selectionRange.start.line = r_symbol.range.start.line; + r_symbol.range = range_of_node(p_class); + r_symbol.range.start.line = MAX(r_symbol.range.start.line, 0); + if (p_class->identifier) { + r_symbol.selectionRange = range_of_node(p_class->identifier); + } r_symbol.detail = "class " + r_symbol.name; - bool is_root_class = &r_symbol == &class_symbol; - r_symbol.documentation = parse_documentation(is_root_class ? 0 : LINE_NUMBER_TO_INDEX(p_class->start_line), is_root_class); + { + String doc = p_class->doc_data.description; + if (!p_class->doc_data.description.is_empty()) { + doc += "\n\n" + p_class->doc_data.description; + } + + if (!p_class->doc_data.tutorials.is_empty()) { + doc += "\n"; + for (const Pair<String, String> &tutorial : p_class->doc_data.tutorials) { + if (tutorial.first.is_empty()) { + doc += vformat("\n@tutorial: %s", tutorial.second); + } else { + doc += vformat("\n@tutorial(%s): %s", tutorial.first, tutorial.second); + } + } + } + r_symbol.documentation = doc; + } for (int i = 0; i < p_class->members.size(); i++) { const ClassNode::Member &m = p_class->members[i]; @@ -166,11 +271,8 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p symbol.name = m.variable->identifier->name; symbol.kind = m.variable->property == VariableNode::PROP_NONE ? lsp::SymbolKind::Variable : lsp::SymbolKind::Property; symbol.deprecated = false; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.variable->start_line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.variable->start_column); - symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.variable->end_line); - symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.variable->end_column); - symbol.selectionRange.start.line = symbol.range.start.line; + symbol.range = range_of_node(m.variable); + symbol.selectionRange = range_of_node(m.variable->identifier); if (m.variable->exported) { symbol.detail += "@export "; } @@ -182,10 +284,31 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p symbol.detail += " = " + m.variable->initializer->reduced_value.to_json_string(); } - symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.variable->start_line)); + symbol.documentation = m.variable->doc_data.description; symbol.uri = uri; symbol.script_path = path; + if (m.variable->initializer && m.variable->initializer->type == GDScriptParser::Node::LAMBDA) { + GDScriptParser::LambdaNode *lambda_node = (GDScriptParser::LambdaNode *)m.variable->initializer; + lsp::DocumentSymbol lambda; + parse_function_symbol(lambda_node->function, lambda); + // Merge lambda into current variable. + symbol.children.append_array(lambda.children); + } + + if (m.variable->getter && m.variable->getter->type == GDScriptParser::Node::FUNCTION) { + lsp::DocumentSymbol get_symbol; + parse_function_symbol(m.variable->getter, get_symbol); + get_symbol.local = true; + symbol.children.push_back(get_symbol); + } + if (m.variable->setter && m.variable->setter->type == GDScriptParser::Node::FUNCTION) { + lsp::DocumentSymbol set_symbol; + parse_function_symbol(m.variable->setter, set_symbol); + set_symbol.local = true; + symbol.children.push_back(set_symbol); + } + r_symbol.children.push_back(symbol); } break; case ClassNode::Member::CONSTANT: { @@ -194,12 +317,9 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p symbol.name = m.constant->identifier->name; symbol.kind = lsp::SymbolKind::Constant; symbol.deprecated = false; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.constant->start_line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.constant->start_column); - symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.constant->end_line); - symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.constant->start_column); - symbol.selectionRange.start.line = LINE_NUMBER_TO_INDEX(m.constant->start_line); - symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.constant->start_line)); + symbol.range = range_of_node(m.constant); + symbol.selectionRange = range_of_node(m.constant->identifier); + symbol.documentation = m.constant->doc_data.description; symbol.uri = uri; symbol.script_path = path; @@ -231,36 +351,14 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p r_symbol.children.push_back(symbol); } break; - case ClassNode::Member::ENUM_VALUE: { - lsp::DocumentSymbol symbol; - - symbol.name = m.enum_value.identifier->name; - symbol.kind = lsp::SymbolKind::EnumMember; - symbol.deprecated = false; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.enum_value.leftmost_column); - symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.enum_value.line); - symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.enum_value.rightmost_column); - symbol.selectionRange.start.line = LINE_NUMBER_TO_INDEX(m.enum_value.line); - symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.enum_value.line)); - symbol.uri = uri; - symbol.script_path = path; - - symbol.detail = symbol.name + " = " + itos(m.enum_value.value); - - r_symbol.children.push_back(symbol); - } break; case ClassNode::Member::SIGNAL: { lsp::DocumentSymbol symbol; symbol.name = m.signal->identifier->name; symbol.kind = lsp::SymbolKind::Event; symbol.deprecated = false; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.signal->start_line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.signal->start_column); - symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.signal->end_line); - symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.signal->end_column); - symbol.selectionRange.start.line = symbol.range.start.line; - symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.signal->start_line)); + symbol.range = range_of_node(m.signal); + symbol.selectionRange = range_of_node(m.signal->identifier); + symbol.documentation = m.signal->doc_data.description; symbol.uri = uri; symbol.script_path = path; symbol.detail = "signal " + String(m.signal->identifier->name) + "("; @@ -272,17 +370,48 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p } symbol.detail += ")"; + for (GDScriptParser::ParameterNode *param : m.signal->parameters) { + lsp::DocumentSymbol param_symbol; + param_symbol.name = param->identifier->name; + param_symbol.kind = lsp::SymbolKind::Variable; + param_symbol.deprecated = false; + param_symbol.local = true; + param_symbol.range = range_of_node(param); + param_symbol.selectionRange = range_of_node(param->identifier); + param_symbol.uri = uri; + param_symbol.script_path = path; + param_symbol.detail = "var " + param_symbol.name; + if (param->get_datatype().is_hard_type()) { + param_symbol.detail += ": " + param->get_datatype().to_string(); + } + symbol.children.push_back(param_symbol); + } + r_symbol.children.push_back(symbol); + } break; + case ClassNode::Member::ENUM_VALUE: { + lsp::DocumentSymbol symbol; + + 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.selectionRange = range_of_node(m.enum_value.identifier); + symbol.documentation = m.enum_value.doc_data.description; + symbol.uri = uri; + symbol.script_path = path; + + symbol.detail = symbol.name + " = " + itos(m.enum_value.value); + r_symbol.children.push_back(symbol); } break; case ClassNode::Member::ENUM: { lsp::DocumentSymbol symbol; + symbol.name = m.m_enum->identifier->name; symbol.kind = lsp::SymbolKind::Enum; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(m.m_enum->start_line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(m.m_enum->start_column); - symbol.range.end.line = LINE_NUMBER_TO_INDEX(m.m_enum->end_line); - symbol.range.end.character = LINE_NUMBER_TO_INDEX(m.m_enum->end_column); - symbol.selectionRange.start.line = symbol.range.start.line; - symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.m_enum->start_line)); + symbol.range = range_of_node(m.m_enum); + symbol.selectionRange = range_of_node(m.m_enum->identifier); + symbol.documentation = m.m_enum->doc_data.description; symbol.uri = uri; symbol.script_path = path; @@ -294,6 +423,25 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p symbol.detail += String(m.m_enum->values[j].identifier->name) + " = " + itos(m.m_enum->values[j].value); } symbol.detail += "}"; + + for (GDScriptParser::EnumNode::Value value : m.m_enum->values) { + lsp::DocumentSymbol child; + + 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.selectionRange = range_of_node(value.identifier); + child.documentation = value.doc_data.description; + child.uri = uri; + child.script_path = path; + + child.detail = child.name + " = " + itos(value.value); + + symbol.children.push_back(child); + } + r_symbol.children.push_back(symbol); } break; case ClassNode::Member::FUNCTION: { @@ -317,32 +465,29 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionNode *p_func, lsp::DocumentSymbol &r_symbol) { const String uri = get_uri(); - r_symbol.name = p_func->identifier->name; - r_symbol.kind = p_func->is_static ? lsp::SymbolKind::Function : lsp::SymbolKind::Method; - r_symbol.detail = "func " + String(p_func->identifier->name) + "("; + bool is_named = p_func->identifier != nullptr; + + r_symbol.name = is_named ? p_func->identifier->name : ""; + r_symbol.kind = (p_func->is_static || p_func->source_lambda != nullptr) ? lsp::SymbolKind::Function : lsp::SymbolKind::Method; + r_symbol.detail = "func"; + if (is_named) { + r_symbol.detail += " " + String(p_func->identifier->name); + } + r_symbol.detail += "("; r_symbol.deprecated = false; - r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_func->start_line); - r_symbol.range.start.character = LINE_NUMBER_TO_INDEX(p_func->start_column); - r_symbol.range.end.line = LINE_NUMBER_TO_INDEX(p_func->start_line); - r_symbol.range.end.character = LINE_NUMBER_TO_INDEX(p_func->end_column); - r_symbol.selectionRange.start.line = r_symbol.range.start.line; - r_symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(p_func->start_line)); + r_symbol.range = range_of_node(p_func); + if (is_named) { + r_symbol.selectionRange = range_of_node(p_func->identifier); + } else { + r_symbol.selectionRange.start = r_symbol.selectionRange.end = r_symbol.range.start; + } + r_symbol.documentation = p_func->doc_data.description; r_symbol.uri = uri; r_symbol.script_path = path; String parameters; for (int i = 0; i < p_func->parameters.size(); i++) { const ParameterNode *parameter = p_func->parameters[i]; - lsp::DocumentSymbol symbol; - symbol.kind = lsp::SymbolKind::Variable; - symbol.name = parameter->identifier->name; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(parameter->start_line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(parameter->start_column); - symbol.range.end.line = LINE_NUMBER_TO_INDEX(parameter->end_line); - symbol.range.end.character = LINE_NUMBER_TO_INDEX(parameter->end_column); - symbol.uri = uri; - symbol.script_path = path; - r_symbol.children.push_back(symbol); if (i > 0) { parameters += ", "; } @@ -387,6 +532,13 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN node_stack.push_back(while_node->loop); } break; + case GDScriptParser::TypeNode::MATCH: { + GDScriptParser::MatchNode *match_node = (GDScriptParser::MatchNode *)node; + for (GDScriptParser::MatchBranchNode *branch_node : match_node->branches) { + node_stack.push_back(branch_node); + } + } break; + case GDScriptParser::TypeNode::MATCH_BRANCH: { GDScriptParser::MatchBranchNode *match_node = (GDScriptParser::MatchBranchNode *)node; node_stack.push_back(match_node->block); @@ -400,20 +552,6 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN } } break; - case GDScriptParser::TypeNode::VARIABLE: { - GDScriptParser::VariableNode *variable_node = (GDScriptParser::VariableNode *)(node); - lsp::DocumentSymbol symbol; - symbol.kind = lsp::SymbolKind::Variable; - symbol.name = variable_node->identifier->name; - symbol.range.start.line = LINE_NUMBER_TO_INDEX(variable_node->start_line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(variable_node->start_column); - symbol.range.end.line = LINE_NUMBER_TO_INDEX(variable_node->end_line); - symbol.range.end.character = LINE_NUMBER_TO_INDEX(variable_node->end_column); - symbol.uri = uri; - symbol.script_path = path; - r_symbol.children.push_back(symbol); - } break; - default: continue; } @@ -426,10 +564,40 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN 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); + switch (local.type) { + case SuiteNode::Local::CONSTANT: + symbol.range = range_of_node(local.constant); + symbol.selectionRange = range_of_node(local.constant->identifier); + break; + case SuiteNode::Local::VARIABLE: + symbol.range = range_of_node(local.variable); + symbol.selectionRange = range_of_node(local.variable->identifier); + if (local.variable->initializer && local.variable->initializer->type == GDScriptParser::Node::LAMBDA) { + GDScriptParser::LambdaNode *lambda_node = (GDScriptParser::LambdaNode *)local.variable->initializer; + lsp::DocumentSymbol lambda; + parse_function_symbol(lambda_node->function, lambda); + // Merge lambda into current variable. + // -> Only interested in new variables, not lambda itself. + symbol.children.append_array(lambda.children); + } + break; + case SuiteNode::Local::PARAMETER: + symbol.range = range_of_node(local.parameter); + symbol.selectionRange = range_of_node(local.parameter->identifier); + break; + case SuiteNode::Local::FOR_VARIABLE: + case SuiteNode::Local::PATTERN_BIND: + symbol.range = range_of_node(local.bind); + symbol.selectionRange = range_of_node(local.bind); + break; + default: + // Fallback. + symbol.range.start = GodotPosition(local.start_line, local.start_column).to_lsp(get_lines()); + symbol.range.end = GodotPosition(local.end_line, local.end_column).to_lsp(get_lines()); + symbol.selectionRange = symbol.range; + break; + } + symbol.local = true; symbol.uri = uri; symbol.script_path = path; symbol.detail = local.type == SuiteNode::Local::CONSTANT ? "const " : "var "; @@ -437,53 +605,19 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN 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); - } - } -} - -String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) { - ERR_FAIL_INDEX_V(p_line, lines.size(), String()); - - List<String> doc_lines; - - if (!p_docs_down) { // inline comment - String inline_comment = lines[p_line]; - int comment_start = inline_comment.find("##"); - if (comment_start != -1) { - inline_comment = inline_comment.substr(comment_start, inline_comment.length()).strip_edges(); - if (inline_comment.length() > 1) { - doc_lines.push_back(inline_comment.substr(2, inline_comment.length())); + switch (local.type) { + case SuiteNode::Local::CONSTANT: + symbol.documentation = local.constant->doc_data.description; + break; + case SuiteNode::Local::VARIABLE: + symbol.documentation = local.variable->doc_data.description; + break; + default: + break; } + r_symbol.children.push_back(symbol); } } - - int step = p_docs_down ? 1 : -1; - int start_line = p_docs_down ? p_line : p_line - 1; - for (int i = start_line; true; i += step) { - if (i < 0 || i >= lines.size()) { - break; - } - - String line_comment = lines[i].strip_edges(true, false); - if (line_comment.begins_with("##")) { - line_comment = line_comment.substr(2, line_comment.length()); - if (p_docs_down) { - doc_lines.push_back(line_comment); - } else { - doc_lines.push_front(line_comment); - } - } else { - break; - } - } - - String doc; - for (const String &E : doc_lines) { - doc += E + "\n"; - } - return doc; } String ExtendGDScriptParser::get_text_for_completion(const lsp::Position &p_cursor) const { @@ -492,7 +626,7 @@ String ExtendGDScriptParser::get_text_for_completion(const lsp::Position &p_curs for (int i = 0; i < len; i++) { if (i == p_cursor.line) { longthing += lines[i].substr(0, p_cursor.character); - longthing += String::chr(0xFFFF); //not unicode, represents the cursor + longthing += String::chr(0xFFFF); // Not unicode, represents the cursor. longthing += lines[i].substr(p_cursor.character, lines[i].size()); } else { longthing += lines[i]; @@ -513,7 +647,7 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c if (i == p_cursor.line) { String line = lines[i]; String first_part = line.substr(0, p_cursor.character); - String last_part = line.substr(p_cursor.character + 1, lines[i].length()); + String last_part = line.substr(p_cursor.character, lines[i].length()); if (!p_symbol.is_empty()) { String left_cursor_text; for (int c = p_cursor.character - 1; c >= 0; c--) { @@ -527,9 +661,9 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c } longthing += first_part; - longthing += String::chr(0xFFFF); //not unicode, represents the cursor + longthing += String::chr(0xFFFF); // Not unicode, represents the cursor. if (p_func_required) { - longthing += "("; // tell the parser this is a function call + longthing += "("; // Tell the parser this is a function call. } longthing += last_part; } else { @@ -544,7 +678,7 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c return longthing; } -String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const { +String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &p_position, lsp::Range &r_range) const { ERR_FAIL_INDEX_V(p_position.line, lines.size(), ""); String line = lines[p_position.line]; if (line.is_empty()) { @@ -552,8 +686,32 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position & } ERR_FAIL_INDEX_V(p_position.character, line.size(), ""); - int start_pos = p_position.character; - for (int c = p_position.character; c >= 0; c--) { + // `p_position` cursor is BETWEEN chars, not ON chars. + // -> + // ```gdscript + // var member| := some_func|(some_variable|) + // ^ ^ ^ + // | | | cursor on `some_variable, position on `)` + // | | + // | | cursor on `some_func`, pos on `(` + // | + // | cursor on `member`, pos on ` ` (space) + // ``` + // -> Move position to previous character if: + // * Position not on valid identifier char. + // * Prev position is valid identifier char. + lsp::Position pos = p_position; + if ( + pos.character >= line.length() // Cursor at end of line. + || (!is_ascii_identifier_char(line[pos.character]) // Not on valid identifier char. + && (pos.character > 0 // Not line start -> there is a prev char. + && is_ascii_identifier_char(line[pos.character - 1]) // Prev is valid identifier char. + ))) { + pos.character--; + } + + int start_pos = pos.character; + for (int c = pos.character; c >= 0; c--) { start_pos = c; char32_t ch = line[c]; bool valid_char = is_ascii_identifier_char(ch); @@ -562,8 +720,8 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position & } } - int end_pos = p_position.character; - for (int c = p_position.character; c < line.length(); c++) { + int end_pos = pos.character; + for (int c = pos.character; c < line.length(); c++) { char32_t ch = line[c]; bool valid_char = is_ascii_identifier_char(ch); if (!valid_char) { @@ -571,9 +729,11 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position & } end_pos = c; } + if (start_pos < end_pos) { - p_offset.x = start_pos - p_position.character; - p_offset.y = end_pos - p_position.character; + r_range.start.line = r_range.end.line = pos.line; + r_range.start.character = start_pos + 1; + r_range.end.character = end_pos + 1; return line.substr(start_pos + 1, end_pos - start_pos); } @@ -584,15 +744,15 @@ String ExtendGDScriptParser::get_uri() const { return GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_uri(path); } -const lsp::DocumentSymbol *ExtendGDScriptParser::search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent) const { +const lsp::DocumentSymbol *ExtendGDScriptParser::search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent, const String &p_symbol_name) const { const lsp::DocumentSymbol *ret = nullptr; if (p_line < p_parent.range.start.line) { return ret; - } else if (p_parent.range.start.line == p_line) { + } else if (p_parent.range.start.line == p_line && (p_symbol_name.is_empty() || p_parent.name == p_symbol_name)) { return &p_parent; } else { for (int i = 0; i < p_parent.children.size(); i++) { - ret = search_symbol_defined_at_line(p_line, p_parent.children[i]); + ret = search_symbol_defined_at_line(p_line, p_parent.children[i], p_symbol_name); if (ret) { break; } @@ -645,11 +805,11 @@ Error ExtendGDScriptParser::get_left_function_call(const lsp::Position &p_positi return ERR_METHOD_NOT_FOUND; } -const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int p_line) const { +const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int p_line, const String &p_symbol_name) const { if (p_line <= 0) { return &class_symbol; } - return search_symbol_defined_at_line(p_line, class_symbol); + return search_symbol_defined_at_line(p_line, class_symbol, p_symbol_name); } const lsp::DocumentSymbol *ExtendGDScriptParser::get_member_symbol(const String &p_name, const String &p_subclass) const { @@ -812,7 +972,7 @@ Dictionary ExtendGDScriptParser::dump_class_api(const GDScriptParser::ClassNode api["name"] = m.signal->identifier->name; Array pars; for (int j = 0; j < m.signal->parameters.size(); j++) { - pars.append(String(m.signal->parameters[i]->identifier->name)); + pars.append(String(m.signal->parameters[j]->identifier->name)); } api["arguments"] = pars; if (const lsp::DocumentSymbol *symbol = get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(m.signal->start_line))) { diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index 4fd27de081..a808f19e5b 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -39,6 +39,9 @@ #ifndef LINE_NUMBER_TO_INDEX #define LINE_NUMBER_TO_INDEX(p_line) ((p_line)-1) #endif +#ifndef COLUMN_NUMBER_TO_INDEX +#define COLUMN_NUMBER_TO_INDEX(p_column) ((p_column)-1) +#endif #ifndef SYMBOL_SEPERATOR #define SYMBOL_SEPERATOR "::" @@ -50,6 +53,64 @@ typedef HashMap<String, const lsp::DocumentSymbol *> ClassMembers; +/** + * Represents a Position as used by GDScript Parser. Used for conversion to and from `lsp::Position`. + * + * Difference to `lsp::Position`: + * * Line & Char/column: 1-based + * * LSP: both 0-based + * * Tabs are expanded to columns using tab size (`text_editor/behavior/indent/size`). + * * LSP: tab is single char + * + * Example: + * ```gdscript + * →→var my_value = 42 + * ``` + * `_` is at: + * * Godot: `column=12` + * * using `indent/size=4` + * * Note: counting starts at `1` + * * LSP: `character=8` + * * Note: counting starts at `0` + */ +struct GodotPosition { + int line; + int column; + + GodotPosition(int p_line, int p_column) : + line(p_line), column(p_column) {} + + lsp::Position to_lsp(const Vector<String> &p_lines) const; + static GodotPosition from_lsp(const lsp::Position p_pos, const Vector<String> &p_lines); + + bool operator==(const GodotPosition &p_other) const { + return line == p_other.line && column == p_other.column; + } + + String to_string() const { + return vformat("(%d,%d)", line, column); + } +}; + +struct GodotRange { + GodotPosition start; + GodotPosition end; + + GodotRange(GodotPosition p_start, GodotPosition p_end) : + start(p_start), end(p_end) {} + + lsp::Range to_lsp(const Vector<String> &p_lines) const; + static GodotRange from_lsp(const lsp::Range &p_range, const Vector<String> &p_lines); + + bool operator==(const GodotRange &p_other) const { + return start == p_other.start && end == p_other.end; + } + + String to_string() const { + return vformat("[%s:%s]", start.to_string(), end.to_string()); + } +}; + class ExtendGDScriptParser : public GDScriptParser { String path; Vector<String> lines; @@ -60,6 +121,8 @@ class ExtendGDScriptParser : public GDScriptParser { ClassMembers members; HashMap<String, ClassMembers> inner_classes; + lsp::Range range_of_node(const GDScriptParser::Node *p_node) const; + void update_diagnostics(); void update_symbols(); @@ -70,8 +133,7 @@ class ExtendGDScriptParser : public GDScriptParser { Dictionary dump_function_api(const GDScriptParser::FunctionNode *p_func) const; Dictionary dump_class_api(const GDScriptParser::ClassNode *p_class) const; - String parse_documentation(int p_line, bool p_docs_down = false); - const lsp::DocumentSymbol *search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent) const; + const lsp::DocumentSymbol *search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent, const String &p_symbol_name = "") const; Array member_completions; @@ -87,10 +149,18 @@ public: String get_text_for_completion(const lsp::Position &p_cursor) const; String get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol = "", bool p_func_required = false) const; - String get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const; + String get_identifier_under_position(const lsp::Position &p_position, lsp::Range &r_range) const; String get_uri() const; - const lsp::DocumentSymbol *get_symbol_defined_at_line(int p_line) const; + /** + * `p_symbol_name` gets ignored if empty. Otherwise symbol must match passed in named. + * + * Necessary when multiple symbols at same line for example with `func`: + * `func handle_arg(arg: int):` + * -> Without `p_symbol_name`: returns `handle_arg`. Even if parameter (`arg`) is wanted. + * With `p_symbol_name`: symbol name MUST match `p_symbol_name`: returns `arg`. + */ + const lsp::DocumentSymbol *get_symbol_defined_at_line(int p_line, const String &p_symbol_name = "") const; const lsp::DocumentSymbol *get_member_symbol(const String &p_name, const String &p_subclass = "") const; const List<lsp::DocumentLink> &get_document_links() const; diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 112db4df3a..8489fc08c1 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -278,6 +278,11 @@ void GDScriptLanguageProtocol::stop() { } void GDScriptLanguageProtocol::notify_client(const String &p_method, const Variant &p_params, int p_client_id) { +#ifdef TESTS_ENABLED + if (clients.is_empty()) { + return; + } +#endif if (p_client_id == -1) { ERR_FAIL_COND_MSG(latest_client_id == -1, "GDScript LSP: Can't notify client as none was connected."); @@ -285,7 +290,7 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia } ERR_FAIL_COND(!clients.has(p_client_id)); Ref<LSPeer> peer = clients.get(p_client_id); - ERR_FAIL_COND(peer == nullptr); + ERR_FAIL_NULL(peer); Dictionary message = make_notification(p_method, p_params); String msg = Variant(message).to_json_string(); @@ -294,6 +299,11 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia } void GDScriptLanguageProtocol::request_client(const String &p_method, const Variant &p_params, int p_client_id) { +#ifdef TESTS_ENABLED + if (clients.is_empty()) { + return; + } +#endif if (p_client_id == -1) { ERR_FAIL_COND_MSG(latest_client_id == -1, "GDScript LSP: Can't notify client as none was connected."); @@ -301,7 +311,7 @@ void GDScriptLanguageProtocol::request_client(const String &p_method, const Vari } ERR_FAIL_COND(!clients.has(p_client_id)); Ref<LSPeer> peer = clients.get(p_client_id); - ERR_FAIL_COND(peer == nullptr); + ERR_FAIL_NULL(peer); Dictionary message = make_request(p_method, p_params, next_server_id); next_server_id++; diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp index 8c44483288..053be7eec2 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -36,6 +36,8 @@ #include "editor/editor_node.h" #include "editor/editor_settings.h" +int GDScriptLanguageServer::port_override = -1; + GDScriptLanguageServer::GDScriptLanguageServer() { _EDITOR_DEF("network/language_server/remote_host", host); _EDITOR_DEF("network/language_server/remote_port", port); @@ -62,7 +64,7 @@ void GDScriptLanguageServer::_notification(int p_what) { case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { String remote_host = String(_EDITOR_GET("network/language_server/remote_host")); - int remote_port = (int)_EDITOR_GET("network/language_server/remote_port"); + int remote_port = (GDScriptLanguageServer::port_override > -1) ? GDScriptLanguageServer::port_override : (int)_EDITOR_GET("network/language_server/remote_port"); bool remote_use_thread = (bool)_EDITOR_GET("network/language_server/use_thread"); if (remote_host != host || remote_port != port || remote_use_thread != use_thread) { stop(); @@ -84,10 +86,10 @@ void GDScriptLanguageServer::thread_main(void *p_userdata) { void GDScriptLanguageServer::start() { host = String(_EDITOR_GET("network/language_server/remote_host")); - port = (int)_EDITOR_GET("network/language_server/remote_port"); + port = (GDScriptLanguageServer::port_override > -1) ? GDScriptLanguageServer::port_override : (int)_EDITOR_GET("network/language_server/remote_port"); use_thread = (bool)_EDITOR_GET("network/language_server/use_thread"); if (protocol.start(port, IPAddress(host)) == OK) { - EditorNode::get_log()->add_message("--- GDScript language server started ---", EditorLog::MSG_TYPE_EDITOR); + EditorNode::get_log()->add_message("--- GDScript language server started on port " + itos(port) + " ---", EditorLog::MSG_TYPE_EDITOR); if (use_thread) { thread_running = true; thread.start(GDScriptLanguageServer::thread_main, this); diff --git a/modules/gdscript/language_server/gdscript_language_server.h b/modules/gdscript/language_server/gdscript_language_server.h index 75f9403a74..e845d139bf 100644 --- a/modules/gdscript/language_server/gdscript_language_server.h +++ b/modules/gdscript/language_server/gdscript_language_server.h @@ -53,6 +53,7 @@ private: void _notification(int p_what); public: + static int port_override; GDScriptLanguageServer(); void start(); void stop(); diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index 92a5f55978..44f605232d 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -50,6 +50,8 @@ void GDScriptTextDocument::_bind_methods() { ClassDB::bind_method(D_METHOD("completion"), &GDScriptTextDocument::completion); ClassDB::bind_method(D_METHOD("resolve"), &GDScriptTextDocument::resolve); ClassDB::bind_method(D_METHOD("rename"), &GDScriptTextDocument::rename); + ClassDB::bind_method(D_METHOD("prepareRename"), &GDScriptTextDocument::prepareRename); + ClassDB::bind_method(D_METHOD("references"), &GDScriptTextDocument::references); ClassDB::bind_method(D_METHOD("foldingRange"), &GDScriptTextDocument::foldingRange); ClassDB::bind_method(D_METHOD("codeLens"), &GDScriptTextDocument::codeLens); ClassDB::bind_method(D_METHOD("documentLink"), &GDScriptTextDocument::documentLink); @@ -108,9 +110,11 @@ void GDScriptTextDocument::didSave(const Variant &p_param) { } else { scr->reload(true); } + scr->update_exports(); ScriptEditor::get_singleton()->reload_scripts(true); ScriptEditor::get_singleton()->update_docs_from_script(scr); + ScriptEditor::get_singleton()->trigger_live_script_reload(); } } @@ -161,11 +165,8 @@ Array GDScriptTextDocument::documentSymbol(const Dictionary &p_params) { String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(uri); Array arr; if (HashMap<String, ExtendGDScriptParser *>::ConstIterator parser = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(path)) { - Vector<lsp::DocumentedSymbolInformation> list; - parser->value->get_symbols().symbol_tree_as_list(uri, list); - for (int i = 0; i < list.size(); i++) { - arr.push_back(list[i].to_json()); - } + lsp::DocumentSymbol symbol = parser->value->get_symbols(); + arr.push_back(symbol.to_json(true)); } return arr; } @@ -253,6 +254,48 @@ Dictionary GDScriptTextDocument::rename(const Dictionary &p_params) { return GDScriptLanguageProtocol::get_singleton()->get_workspace()->rename(params, new_name); } +Variant GDScriptTextDocument::prepareRename(const Dictionary &p_params) { + lsp::TextDocumentPositionParams params; + params.load(p_params); + + lsp::DocumentSymbol symbol; + lsp::Range range; + if (GDScriptLanguageProtocol::get_singleton()->get_workspace()->can_rename(params, symbol, range)) { + return Variant(range.to_json()); + } + + // `null` -> rename not valid at current location. + return Variant(); +} + +Array GDScriptTextDocument::references(const Dictionary &p_params) { + Array res; + + lsp::ReferenceParams params; + params.load(p_params); + + const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params); + if (symbol) { + Vector<lsp::Location> usages = GDScriptLanguageProtocol::get_singleton()->get_workspace()->find_all_usages(*symbol); + res.resize(usages.size()); + int declaration_adjustment = 0; + for (int i = 0; i < usages.size(); i++) { + lsp::Location usage = usages[i]; + if (!params.context.includeDeclaration && usage.range == symbol->range) { + declaration_adjustment++; + continue; + } + res[i - declaration_adjustment] = usages[i].to_json(); + } + + if (declaration_adjustment > 0) { + res.resize(res.size() - declaration_adjustment); + } + } + + return res; +} + Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { lsp::CompletionItem item; item.load(p_params); @@ -305,6 +348,15 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { } } + if (item.kind == lsp::CompletionItemKind::Method) { + bool is_trigger_character = params.context.triggerKind == lsp::CompletionTriggerKind::TriggerCharacter; + bool is_quote_character = params.context.triggerCharacter == "\"" || params.context.triggerCharacter == "'"; + + if (is_trigger_character && is_quote_character && item.insertText.is_quoted()) { + item.insertText = item.insertText.unquote(); + } + } + return item.to_json(true); } @@ -450,7 +502,7 @@ Array GDScriptTextDocument::find_symbols(const lsp::TextDocumentPositionParams & if (symbol) { lsp::Location location; location.uri = symbol->uri; - location.range = symbol->range; + location.range = symbol->selectionRange; const String &path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(symbol->uri); if (file_checker->file_exists(path)) { arr.push_back(location.to_json()); @@ -464,7 +516,7 @@ Array GDScriptTextDocument::find_symbols(const lsp::TextDocumentPositionParams & if (!s->uri.is_empty()) { lsp::Location location; location.uri = s->uri; - location.range = s->range; + location.range = s->selectionRange; arr.push_back(location.to_json()); r_list.push_back(s); } diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h index 0121101db2..cfd0490f0a 100644 --- a/modules/gdscript/language_server/gdscript_text_document.h +++ b/modules/gdscript/language_server/gdscript_text_document.h @@ -65,6 +65,8 @@ public: Array completion(const Dictionary &p_params); Dictionary resolve(const Dictionary &p_params); Dictionary rename(const Dictionary &p_params); + Variant prepareRename(const Dictionary &p_params); + Array references(const Dictionary &p_params); Array foldingRange(const Dictionary &p_params); Array codeLens(const Dictionary &p_params); Array documentLink(const Dictionary &p_params); diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 9f848b02f5..81933c8c87 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -46,7 +46,6 @@ void GDScriptWorkspace::_bind_methods() { ClassDB::bind_method(D_METHOD("apply_new_signal"), &GDScriptWorkspace::apply_new_signal); 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); ClassDB::bind_method(D_METHOD("get_file_path", "uri"), &GDScriptWorkspace::get_file_path); @@ -182,35 +181,33 @@ const lsp::DocumentSymbol *GDScriptWorkspace::get_parameter_symbol(const lsp::Do return nullptr; } -const lsp::DocumentSymbol *GDScriptWorkspace::get_local_symbol(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier) { - const lsp::DocumentSymbol *class_symbol = &p_parser->get_symbols(); +const lsp::DocumentSymbol *GDScriptWorkspace::get_local_symbol_at(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier, const lsp::Position p_position) { + // Go down and pick closest `DocumentSymbol` with `p_symbol_identifier`. - for (int i = 0; i < class_symbol->children.size(); ++i) { - int kind = class_symbol->children[i].kind; - switch (kind) { - case lsp::SymbolKind::Function: - case lsp::SymbolKind::Method: - case lsp::SymbolKind::Class: { - const lsp::DocumentSymbol *function_symbol = &class_symbol->children[i]; + const lsp::DocumentSymbol *current = &p_parser->get_symbols(); + const lsp::DocumentSymbol *best_match = nullptr; - for (int l = 0; l < function_symbol->children.size(); ++l) { - const lsp::DocumentSymbol *local = &function_symbol->children[l]; - if (!local->detail.is_empty() && local->name == p_symbol_identifier) { - return local; - } - } - } break; + while (current) { + if (current->name == p_symbol_identifier) { + if (current->selectionRange.contains(p_position)) { + // Exact match: pos is ON symbol decl identifier. + return current; + } - case lsp::SymbolKind::Variable: { - const lsp::DocumentSymbol *variable_symbol = &class_symbol->children[i]; - if (variable_symbol->name == p_symbol_identifier) { - return variable_symbol; - } - } break; + best_match = current; + } + + const lsp::DocumentSymbol *parent = current; + current = nullptr; + for (const lsp::DocumentSymbol &child : parent->children) { + if (child.range.contains(p_position)) { + current = &child; + break; + } } } - return nullptr; + return best_match; } void GDScriptWorkspace::reload_all_workspace_scripts() { @@ -275,25 +272,6 @@ ExtendGDScriptParser *GDScriptWorkspace::get_parse_result(const String &p_path) return nullptr; } -Array GDScriptWorkspace::symbol(const Dictionary &p_params) { - String query = p_params["query"]; - Array arr; - if (!query.is_empty()) { - for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) { - Vector<lsp::DocumentedSymbolInformation> script_symbols; - E.value->get_symbols().symbol_tree_as_list(E.key, script_symbols); - for (int i = 0; i < script_symbols.size(); ++i) { - if (query.is_subsequence_ofn(script_symbols[i].name)) { - lsp::DocumentedSymbolInformation symbol = script_symbols[i]; - symbol.location.uri = get_file_uri(symbol.location.uri); - arr.push_back(symbol.to_json()); - } - } - } - } - return arr; -} - Error GDScriptWorkspace::initialize() { if (initialized) { return OK; @@ -423,7 +401,7 @@ Error GDScriptWorkspace::initialize() { native_members.insert(E.key, members); } - // cache member completions + // Cache member completions. for (const KeyValue<String, ExtendGDScriptParser *> &S : scripts) { S.value->get_member_completions(); } @@ -458,48 +436,110 @@ Error GDScriptWorkspace::parse_script(const String &p_path, const String &p_cont return err; } -Dictionary GDScriptWorkspace::rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name) { - Error err; - String path = get_file_path(p_doc_pos.textDocument.uri); +static bool is_valid_rename_target(const lsp::DocumentSymbol *p_symbol) { + // Must be valid symbol. + if (!p_symbol) { + return false; + } + + // Cannot rename builtin. + if (!p_symbol->native_class.is_empty()) { + return false; + } + + // Source must be available. + if (p_symbol->script_path.is_empty()) { + return false; + } + return true; +} + +Dictionary GDScriptWorkspace::rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name) { lsp::WorkspaceEdit edit; - List<String> paths; - list_script_files("res://", paths); + const lsp::DocumentSymbol *reference_symbol = resolve_symbol(p_doc_pos); + if (is_valid_rename_target(reference_symbol)) { + Vector<lsp::Location> usages = find_all_usages(*reference_symbol); + for (int i = 0; i < usages.size(); ++i) { + lsp::Location loc = usages[i]; + + edit.add_change(loc.uri, loc.range.start.line, loc.range.start.character, loc.range.end.character, new_name); + } + } + + return edit.to_json(); +} +bool GDScriptWorkspace::can_rename(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::DocumentSymbol &r_symbol, lsp::Range &r_range) { const lsp::DocumentSymbol *reference_symbol = resolve_symbol(p_doc_pos); - if (reference_symbol) { - String identifier = reference_symbol->name; + if (!is_valid_rename_target(reference_symbol)) { + return false; + } - for (List<String>::Element *PE = paths.front(); PE; PE = PE->next()) { - PackedStringArray content = FileAccess::get_file_as_string(PE->get(), &err).split("\n"); - for (int i = 0; i < content.size(); ++i) { - String line = content[i]; + String path = get_file_path(p_doc_pos.textDocument.uri); + if (const ExtendGDScriptParser *parser = get_parse_result(path)) { + parser->get_identifier_under_position(p_doc_pos.position, r_range); + r_symbol = *reference_symbol; + return true; + } - int character = line.find(identifier); - while (character > -1) { - lsp::TextDocumentPositionParams params; + return false; +} - lsp::TextDocumentIdentifier text_doc; - text_doc.uri = get_file_uri(PE->get()); +Vector<lsp::Location> GDScriptWorkspace::find_usages_in_file(const lsp::DocumentSymbol &p_symbol, const String &p_file_path) { + Vector<lsp::Location> usages; - params.textDocument = text_doc; - params.position.line = i; - params.position.character = character; + String identifier = p_symbol.name; + if (const ExtendGDScriptParser *parser = get_parse_result(p_file_path)) { + const PackedStringArray &content = parser->get_lines(); + for (int i = 0; i < content.size(); ++i) { + String line = content[i]; - const lsp::DocumentSymbol *other_symbol = resolve_symbol(params); + int character = line.find(identifier); + while (character > -1) { + lsp::TextDocumentPositionParams params; - if (other_symbol == reference_symbol) { - edit.add_change(text_doc.uri, i, character, character + identifier.length(), new_name); - } + lsp::TextDocumentIdentifier text_doc; + text_doc.uri = get_file_uri(p_file_path); - character = line.find(identifier, character + 1); + params.textDocument = text_doc; + params.position.line = i; + params.position.character = character; + + const lsp::DocumentSymbol *other_symbol = resolve_symbol(params); + + if (other_symbol == &p_symbol) { + lsp::Location loc; + loc.uri = text_doc.uri; + loc.range.start = params.position; + loc.range.end.line = params.position.line; + loc.range.end.character = params.position.character + identifier.length(); + usages.append(loc); } + + character = line.find(identifier, character + 1); } } } - return edit.to_json(); + return usages; +} + +Vector<lsp::Location> GDScriptWorkspace::find_all_usages(const lsp::DocumentSymbol &p_symbol) { + if (p_symbol.local) { + // Only search in current document. + return find_usages_in_file(p_symbol, p_symbol.script_path); + } + // Search in all documents. + List<String> paths; + list_script_files("res://", paths); + + Vector<lsp::Location> usages; + for (List<String>::Element *PE = paths.front(); PE; PE = PE->next()) { + usages.append_array(find_usages_in_file(p_symbol, PE->get())); + } + return usages; } Error GDScriptWorkspace::parse_local_script(const String &p_path) { @@ -636,9 +676,9 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu lsp::Position pos = p_doc_pos.position; if (symbol_identifier.is_empty()) { - Vector2i offset; - symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, offset); - pos.character += offset.y; + lsp::Range range; + symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, range); + pos.character = range.end.character; } if (!symbol_identifier.is_empty()) { @@ -661,7 +701,7 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu } if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) { - symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location)); + symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location), symbol_identifier); if (symbol) { switch (symbol->kind) { @@ -670,10 +710,6 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu symbol = get_parameter_symbol(symbol, symbol_identifier); } } break; - - case lsp::SymbolKind::Variable: { - symbol = get_local_symbol(parser, symbol_identifier); - } break; } } } @@ -686,10 +722,9 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu symbol = get_native_symbol(ret.class_name, member); } } else { - symbol = parser->get_member_symbol(symbol_identifier); - + symbol = get_local_symbol_at(parser, symbol_identifier, p_doc_pos.position); if (!symbol) { - symbol = get_local_symbol(parser, symbol_identifier); + symbol = parser->get_member_symbol(symbol_identifier); } } } @@ -703,8 +738,8 @@ void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionP String path = get_file_path(p_doc_pos.textDocument.uri); if (const ExtendGDScriptParser *parser = get_parse_result(path)) { String symbol_identifier; - Vector2i offset; - symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, offset); + lsp::Range range; + symbol_identifier = parser->get_identifier_under_position(p_doc_pos.position, range); for (const KeyValue<StringName, ClassMembers> &E : native_members) { const ClassMembers &members = native_members.get(E.key); diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h index 80653778fb..0b2d43b817 100644 --- a/modules/gdscript/language_server/gdscript_workspace.h +++ b/modules/gdscript/language_server/gdscript_workspace.h @@ -54,7 +54,7 @@ protected: const lsp::DocumentSymbol *get_native_symbol(const String &p_class, const String &p_member = "") const; const lsp::DocumentSymbol *get_script_symbol(const String &p_path) const; const lsp::DocumentSymbol *get_parameter_symbol(const lsp::DocumentSymbol *p_parent, const String &symbol_identifier); - const lsp::DocumentSymbol *get_local_symbol(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier); + const lsp::DocumentSymbol *get_local_symbol_at(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier, const lsp::Position p_position); void reload_all_workspace_scripts(); @@ -74,9 +74,6 @@ public: HashMap<StringName, ClassMembers> native_members; public: - Array symbol(const Dictionary &p_params); - -public: Error initialize(); Error parse_script(const String &p_path, const String &p_content); @@ -96,6 +93,9 @@ public: Error resolve_signature(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::SignatureHelp &r_signature); void did_delete_files(const Dictionary &p_params); Dictionary rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name); + bool can_rename(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::DocumentSymbol &r_symbol, lsp::Range &r_range); + Vector<lsp::Location> find_usages_in_file(const lsp::DocumentSymbol &p_symbol, const String &p_file_path); + Vector<lsp::Location> find_all_usages(const lsp::DocumentSymbol &p_symbol); GDScriptWorkspace(); ~GDScriptWorkspace(); diff --git a/modules/gdscript/language_server/godot_lsp.h b/modules/gdscript/language_server/godot_lsp.h index 3782945e07..e09adb74bd 100644 --- a/modules/gdscript/language_server/godot_lsp.h +++ b/modules/gdscript/language_server/godot_lsp.h @@ -83,6 +83,14 @@ struct Position { */ int character = 0; + _FORCE_INLINE_ bool operator==(const Position &p_other) const { + return line == p_other.line && character == p_other.character; + } + + String to_string() const { + return vformat("(%d,%d)", line, character); + } + _FORCE_INLINE_ void load(const Dictionary &p_params) { line = p_params["line"]; character = p_params["character"]; @@ -112,6 +120,27 @@ struct Range { */ Position end; + _FORCE_INLINE_ bool operator==(const Range &p_other) const { + return start == p_other.start && end == p_other.end; + } + + bool contains(const Position &p_pos) const { + // Inside line range. + if (start.line <= p_pos.line && p_pos.line <= end.line) { + // If on start line: must come after start char. + bool start_ok = p_pos.line == start.line ? start.character <= p_pos.character : true; + // If on end line: must come before end char. + bool end_ok = p_pos.line == end.line ? p_pos.character <= end.character : true; + return start_ok && end_ok; + } else { + return false; + } + } + + String to_string() const { + return vformat("[%s:%s]", start.to_string(), end.to_string()); + } + _FORCE_INLINE_ void load(const Dictionary &p_params) { start.load(p_params["start"]); end.load(p_params["end"]); @@ -203,6 +232,17 @@ struct TextDocumentPositionParams { } }; +struct ReferenceContext { + /** + * Include the declaration of the current symbol. + */ + bool includeDeclaration; +}; + +struct ReferenceParams : TextDocumentPositionParams { + ReferenceContext context; +}; + struct DocumentLinkParams { /** * The document to provide document links for. @@ -343,8 +383,8 @@ struct Command { } }; -// Use namespace instead of enumeration to follow the LSP specifications -// lsp::EnumName::EnumValue is OK but lsp::EnumValue is not +// Use namespace instead of enumeration to follow the LSP specifications. +// `lsp::EnumName::EnumValue` is OK but `lsp::EnumValue` is not. namespace TextDocumentSyncKind { /** @@ -436,7 +476,7 @@ struct RenameOptions { /** * Renames should be checked and tested before being executed. */ - bool prepareProvider = false; + bool prepareProvider = true; Dictionary to_json() { Dictionary dict; @@ -794,12 +834,12 @@ static const String Markdown = "markdown"; */ struct MarkupContent { /** - * The type of the Markup + * The type of the Markup. */ String kind; /** - * The content itself + * The content itself. */ String value; @@ -821,8 +861,8 @@ 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, RefCounted etc. +// `lsp::EnumName::EnumValue` is OK but `lsp::EnumValue` is not. +// And here C++ compilers are unhappy with our enumeration name like `Color`, `File`, `RefCounted` etc. /** * The kind of a completion entry. */ @@ -854,7 +894,7 @@ static const int Operator = 24; static const int TypeParameter = 25; }; // namespace CompletionItemKind -// Use namespace instead of enumeration to follow the LSP specifications +// Use namespace instead of enumeration to follow the LSP specifications. /** * Defines whether the insert text in a completion item should be interpreted as * plain text or a snippet. @@ -1070,8 +1110,8 @@ struct CompletionList { }; // 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 String, Array, Object etc +// `lsp::EnumName::EnumValue` is OK but `lsp::EnumValue` is not +// And here C++ compilers are unhappy with our enumeration name like `String`, `Array`, `Object` etc /** * A symbol kind. */ @@ -1105,70 +1145,6 @@ static const int TypeParameter = 26; }; // namespace SymbolKind /** - * Represents information about programming constructs like variables, classes, - * interfaces etc. - */ -struct SymbolInformation { - /** - * The name of this symbol. - */ - String name; - - /** - * The kind of this symbol. - */ - int kind = SymbolKind::File; - - /** - * Indicates if this symbol is deprecated. - */ - bool deprecated = false; - - /** - * The location of this symbol. The location's range is used by a tool - * to reveal the location in the editor. If the symbol is selected in the - * tool the range's start information is used to position the cursor. So - * the range usually spans more then the actual symbol's name and does - * normally include things like visibility modifiers. - * - * The range doesn't have to denote a node range in the sense of a abstract - * syntax tree. It can therefore not be used to re-construct a hierarchy of - * the symbols. - */ - Location location; - - /** - * The name of the symbol containing this symbol. This information is for - * user interface purposes (e.g. to render a qualifier in the user interface - * if necessary). It can't be used to re-infer a hierarchy for the document - * symbols. - */ - String containerName; - - _FORCE_INLINE_ Dictionary to_json() const { - Dictionary dict; - dict["name"] = name; - dict["kind"] = kind; - dict["deprecated"] = deprecated; - dict["location"] = location.to_json(); - dict["containerName"] = containerName; - return dict; - } -}; - -struct DocumentedSymbolInformation : public SymbolInformation { - /** - * A human-readable string with additional information - */ - String detail; - - /** - * A human-readable string that represents a doc-comment. - */ - String documentation; -}; - -/** * Represents programming constructs like variables, classes, interfaces etc. that appear in a document. Document symbols can be * hierarchical and they have two ranges: one that encloses its definition and one that points to its most interesting range, * e.g. the range of an identifier. @@ -1186,12 +1162,12 @@ struct DocumentSymbol { String detail; /** - * Documentation for this symbol + * Documentation for this symbol. */ String documentation; /** - * Class name for the native symbols + * Class name for the native symbols. */ String native_class; @@ -1206,6 +1182,13 @@ struct DocumentSymbol { bool deprecated = false; /** + * If `true`: Symbol is local to script and cannot be accessed somewhere else. + * + * For example: local variable inside a `func`. + */ + bool local = false; + + /** * The range enclosing this symbol not including leading/trailing whitespace but everything else * like comments. This information is typically used to determine if the clients cursor is * inside the symbol to reveal in the symbol in the UI. @@ -1238,35 +1221,21 @@ struct DocumentSymbol { dict["documentation"] = documentation; dict["native_class"] = native_class; } - Array arr; - arr.resize(children.size()); - for (int i = 0; i < children.size(); i++) { - arr[i] = children[i].to_json(with_doc); + if (!children.is_empty()) { + Array arr; + for (int i = 0; i < children.size(); i++) { + if (children[i].local) { + continue; + } + arr.push_back(children[i].to_json(with_doc)); + } + if (!children.is_empty()) { + dict["children"] = arr; + } } - dict["children"] = arr; return dict; } - void symbol_tree_as_list(const String &p_uri, Vector<DocumentedSymbolInformation> &r_list, const String &p_container = "", bool p_join_name = false) const { - DocumentedSymbolInformation si; - if (p_join_name && !p_container.is_empty()) { - si.name = p_container + ">" + name; - } else { - si.name = name; - } - si.kind = kind; - si.containerName = p_container; - si.deprecated = deprecated; - si.location.uri = p_uri; - si.location.range = range; - si.detail = detail; - si.documentation = documentation; - r_list.push_back(si); - for (int i = 0; i < children.size(); i++) { - children[i].symbol_tree_as_list(p_uri, r_list, si.name, p_join_name); - } - } - _FORCE_INLINE_ MarkupContent render() const { MarkupContent markdown; if (detail.length()) { @@ -1460,6 +1429,17 @@ struct CompletionParams : public TextDocumentPositionParams { TextDocumentPositionParams::load(p_params); context.load(p_params["context"]); } + + Dictionary to_json() { + Dictionary ctx; + ctx["triggerCharacter"] = context.triggerCharacter; + ctx["triggerKind"] = context.triggerKind; + + Dictionary dict; + dict = TextDocumentPositionParams::to_json(); + dict["context"] = ctx; + return dict; + } }; /** @@ -1750,7 +1730,7 @@ struct ServerCapabilities { /** * The server provides find references support. */ - bool referencesProvider = false; + bool referencesProvider = true; /** * The server provides document highlight support. diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 874cbc6ee8..f91dc83f2c 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -149,6 +149,10 @@ GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_l // Set all warning levels to "Warn" in order to test them properly, even the ones that default to error. ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true); for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) { + if (i == GDScriptWarning::UNTYPED_DECLARATION || i == GDScriptWarning::INFERRED_DECLARATION) { + // TODO: Add ability for test scripts to specify which warnings to enable/disable for testing. + continue; + } String warning_setting = GDScriptWarning::get_settings_path_from_code((GDScriptWarning::Code)i); ProjectSettings::get_singleton()->set_setting(warning_setting, (int)GDScriptWarning::WARN); } diff --git a/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.gd b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.gd new file mode 100644 index 0000000000..87d1b9ea18 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.gd @@ -0,0 +1,7 @@ +# GH-73283 + +class MyClass: + pass + +func test(): + MyClass.not_existing_method() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.out b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.out new file mode 100644 index 0000000000..7340058dd4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/call_not_existing_static_method.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Static function "not_existing_method()" not found in base "MyClass". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.gd b/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.gd new file mode 100644 index 0000000000..7e1efb8d1b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.gd @@ -0,0 +1,2 @@ +func test(): + Time.new() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.out b/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.out new file mode 100644 index 0000000000..bc4875d908 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/engine_singleton_instantiate.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot construct native class "Time" because it is an engine singleton. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_1.gd b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_1.gd new file mode 100644 index 0000000000..05aa726a05 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_1.gd @@ -0,0 +1,8 @@ +# GH-82809 + +extends Resource + +@export var node: Node + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_1.out b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_1.out new file mode 100644 index 0000000000..9a45bbb515 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_1.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Node export is only supported in Node-derived classes, but the current class inherits "Resource". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_2.gd b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_2.gd new file mode 100644 index 0000000000..c210e7c043 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_2.gd @@ -0,0 +1,9 @@ +# GH-82809 + +extends Node + +class Inner: + @export var node: Node + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_2.out b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_2.out new file mode 100644 index 0000000000..3da6d6d7ac --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_2.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Node export is only supported in Node-derived classes, but the current class inherits "RefCounted". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_3.gd b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_3.gd new file mode 100644 index 0000000000..6f2c104643 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_3.gd @@ -0,0 +1,8 @@ +# GH-82809 + +extends Resource + +@export var node_array: Array[Node] + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_3.out b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_3.out new file mode 100644 index 0000000000..9a45bbb515 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/export_node_in_non_node_derived_class_3.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Node export is only supported in Node-derived classes, but the current class inherits "Resource". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.gd b/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.gd new file mode 100644 index 0000000000..a46b6d8658 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.gd @@ -0,0 +1,6 @@ +# GH-82081 + +extends Time + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.out b/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.out new file mode 100644 index 0000000000..7c26dea56e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_engine_singleton.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot inherit native class "Time" because it is an engine singleton. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.gd new file mode 100644 index 0000000000..db3f3f4c72 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.gd @@ -0,0 +1,5 @@ +# GH-82021 + +func test(): + for x: String in [1, 2, 3]: + print(x) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.out new file mode 100644 index 0000000000..0bb654e7e2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_array.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot include a value of type "int" as "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.gd new file mode 100644 index 0000000000..fdf22f6843 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.gd @@ -0,0 +1,10 @@ +class A: + func f(_p: Object): + pass + +class B extends A: + func f(_p: Node): + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.out b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.out new file mode 100644 index 0000000000..c6a7e40e8c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_1.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f(Object) -> Variant". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.gd new file mode 100644 index 0000000000..e4094f1d76 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.gd @@ -0,0 +1,10 @@ +class A: + func f(_p: Variant): + pass + +class B extends A: + func f(_p: Node): # No `is_type_compatible()` misuse. + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.out b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.out new file mode 100644 index 0000000000..52a6efc6fc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_2.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f(Variant) -> Variant". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.gd new file mode 100644 index 0000000000..17663da4f6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.gd @@ -0,0 +1,10 @@ +class A: + func f(_p: int): + pass + +class B extends A: + func f(_p: float): # No implicit conversion. + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.out b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.out new file mode 100644 index 0000000000..7a6207fd45 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_param_type_invalid_contravariance_3.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f(int) -> Variant". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.gd new file mode 100644 index 0000000000..6dfa75ecbc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.gd @@ -0,0 +1,10 @@ +class A: + func f() -> Node: + return null + +class B extends A: + func f() -> Object: + return null + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.out new file mode 100644 index 0000000000..e680b2bd77 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_1.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f() -> Node". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.gd new file mode 100644 index 0000000000..366494b94f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.gd @@ -0,0 +1,10 @@ +class A: + func f() -> Node: + return null + +class B extends A: + func f() -> Variant: # No `is_type_compatible()` misuse. + return null + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.out new file mode 100644 index 0000000000..e680b2bd77 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_2.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f() -> Node". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.gd new file mode 100644 index 0000000000..2cb4e7c616 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.gd @@ -0,0 +1,10 @@ +class A: + func f() -> Node: + return null + +class B extends A: + func f() -> void: # No `is_type_compatible()` misuse. + return + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.out new file mode 100644 index 0000000000..e680b2bd77 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_3.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f() -> Node". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.gd new file mode 100644 index 0000000000..2cabce46f5 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.gd @@ -0,0 +1,10 @@ +class A: + func f() -> float: + return 0.0 + +class B extends A: + func f() -> int: # No implicit conversion. + return 0 + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.out b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.out new file mode 100644 index 0000000000..72f2c493d4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_return_type_invalid_covariance_4.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The function signature doesn't match the parent. Parent signature is "f() -> float". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/gd_utility_function_wrong_arg.gd b/modules/gdscript/tests/scripts/analyzer/errors/gd_utility_function_wrong_arg.gd new file mode 100644 index 0000000000..c06fbd89ff --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/gd_utility_function_wrong_arg.gd @@ -0,0 +1,2 @@ +func test(): + print(len(Color())) # GDScript utility function. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/gd_utility_function_wrong_arg.out b/modules/gdscript/tests/scripts/analyzer/errors/gd_utility_function_wrong_arg.out new file mode 100644 index 0000000000..9cb04f6240 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/gd_utility_function_wrong_arg.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid argument for "len()" function: Value of type 'Color' can't provide a length. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.gd b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.gd new file mode 100644 index 0000000000..1dcb9fc36a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.gd @@ -0,0 +1,4 @@ +func test(): + match 0: + _ when a == 0: + print("a does not exist") diff --git a/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.out b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.out new file mode 100644 index 0000000000..c5f0a90d6a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/match_guard_invalid_expression.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Identifier "a" not declared in the current scope. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.gd b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.gd new file mode 100644 index 0000000000..1600c3001f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.gd @@ -0,0 +1,4 @@ +# GH-73213 + +func test(): + print(Object()) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.out b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.out new file mode 100644 index 0000000000..27668fcd48 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/object_invalid_constructor.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid constructor "Object()", use "Object.new()" instead. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.gd new file mode 100644 index 0000000000..a98f69f3ac --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.gd @@ -0,0 +1,14 @@ +# GH-83468 + +func non_static_func(): + pass + +static func static_func(): + var f := func (): + var g := func (): + non_static_func() + g.call() + f.call() + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.out b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.out new file mode 100644 index 0000000000..b78f131345 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot call non-static function "non_static_func()" from static function "static_func()". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.gd new file mode 100644 index 0000000000..7af9ff274c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.gd @@ -0,0 +1,15 @@ +# GH-83468 + +func non_static_func(): + pass + +static func static_func( + f := func (): + var g := func (): + non_static_func() + g.call() +): + f.call() + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.out b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.out new file mode 100644 index 0000000000..b78f131345 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_func_call_non_static_in_lambda_param.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot call non-static function "non_static_func()" from static function "static_func()". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.gd new file mode 100644 index 0000000000..5130973bd2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.gd @@ -0,0 +1,14 @@ +# GH-83468 + +func non_static_func(): + pass + +static var static_var = func (): + var f := func (): + var g := func (): + non_static_func() + g.call() + f.call() + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.out b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.out new file mode 100644 index 0000000000..c0308c81f3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot call non-static function "non_static_func()" from a static variable initializer. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.gd new file mode 100644 index 0000000000..2d15b4e3e5 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.gd @@ -0,0 +1,15 @@ +# GH-83468 + +func non_static_func(): + pass + +static var static_var: + set(_value): + var f := func (): + var g := func (): + non_static_func() + g.call() + f.call() + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.out b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.out new file mode 100644 index 0000000000..cdf3ab2aeb --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_call_non_static_in_lambda_setter.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot call non-static function "non_static_func()" from static function "@static_var_setter()". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out index f1e9ec34f2..81554ec707 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot call non-static function "non_static()" for static variable initializer. +Cannot call non-static function "non_static()" from a static variable initializer. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd index 25cde1d40b..7cc5aaf44f 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_array_init_with_unconvertable_in_literal.gd @@ -1,4 +1,4 @@ func test(): - var unconvertable := 1 - var typed: Array[Object] = [unconvertable] + var unconvertible := 1 + var typed: Array[Object] = [unconvertible] print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/utility_function_wrong_arg.gd b/modules/gdscript/tests/scripts/analyzer/errors/utility_function_wrong_arg.gd new file mode 100644 index 0000000000..dc6e26e682 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/utility_function_wrong_arg.gd @@ -0,0 +1,2 @@ +func test(): + print(floor(Color())) # Built-in utility function. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/utility_function_wrong_arg.out b/modules/gdscript/tests/scripts/analyzer/errors/utility_function_wrong_arg.out new file mode 100644 index 0000000000..27d2504dd0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/utility_function_wrong_arg.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid argument for "floor()" function: Argument "x" must be "int", "float", "Vector2", "Vector2i", "Vector3", "Vector3i", "Vector4", or "Vector4i". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd new file mode 100644 index 0000000000..57dfffdbee --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.gd @@ -0,0 +1,5 @@ +func _init(): + super() + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out new file mode 100644 index 0000000000..e68759223c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/virtual_super_not_implemented.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot call the parent class' virtual function "_init()" because it hasn't been defined. diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd new file mode 100644 index 0000000000..dafd2ec0c8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd @@ -0,0 +1,17 @@ +class_name TestExportEnumAsDictionary + +enum MyEnum {A, B, C} + +const Utils = preload("../../utils.notest.gd") + +@export var x1 = MyEnum +@export var x2 = MyEnum.A +@export var x3 := MyEnum +@export var x4 := MyEnum.A +@export var x5: MyEnum + +func test(): + for property in get_property_list(): + if property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE: + print(Utils.get_property_signature(property)) + print(" ", Utils.get_property_additional_info(property)) diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out new file mode 100644 index 0000000000..f1a13f1045 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out @@ -0,0 +1,11 @@ +GDTEST_OK +@export var x1: Dictionary + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE +@export var x2: TestExportEnumAsDictionary.MyEnum + hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM +@export var x3: Dictionary + hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE +@export var x4: TestExportEnumAsDictionary.MyEnum + hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM +@export var x5: TestExportEnumAsDictionary.MyEnum + hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.gd b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.gd new file mode 100644 index 0000000000..a43c233625 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.gd @@ -0,0 +1,20 @@ +class A: + func int_to_variant(_p: int): pass + func node_to_variant(_p: Node): pass + func node_2d_to_node(_p: Node2D): pass + + func variant_to_untyped(_p: Variant): pass + func int_to_untyped(_p: int): pass + func node_to_untyped(_p: Node): pass + +class B extends A: + func int_to_variant(_p: Variant): pass + func node_to_variant(_p: Variant): pass + func node_2d_to_node(_p: Node): pass + + func variant_to_untyped(_p): pass + func int_to_untyped(_p): pass + func node_to_untyped(_p): pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.out b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/function_param_type_contravariance.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.gd b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.gd new file mode 100644 index 0000000000..4de50b6731 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.gd @@ -0,0 +1,32 @@ +class A: + func variant_to_int() -> Variant: return 0 + func variant_to_node() -> Variant: return null + func node_to_node_2d() -> Node: return null + + func untyped_to_void(): pass + func untyped_to_variant(): pass + func untyped_to_int(): pass + func untyped_to_node(): pass + + func void_to_untyped() -> void: pass + func variant_to_untyped() -> Variant: return null + func int_to_untyped() -> int: return 0 + func node_to_untyped() -> Node: return null + +class B extends A: + func variant_to_int() -> int: return 0 + func variant_to_node() -> Node: return null + func node_to_node_2d() -> Node2D: return null + + func untyped_to_void() -> void: pass + func untyped_to_variant() -> Variant: return null + func untyped_to_int() -> int: return 0 + func untyped_to_node() -> Node: return null + + func void_to_untyped(): pass + func variant_to_untyped(): pass + func int_to_untyped(): pass + func node_to_untyped(): pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.out b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/function_return_type_covariance.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd index b447180ea8..d0f895d784 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd @@ -23,6 +23,7 @@ func test() -> void: typed = variant() inferred = variant() + @warning_ignore("unsafe_call_argument") # TODO: Hard vs Weak vs Unknown. param_weak(typed) param_typed(typed) param_inferred(typed) diff --git a/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd index 5a413e2015..08e7dc590e 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd @@ -6,10 +6,12 @@ var prop = null func check_arg(arg = null) -> void: if arg != null: + @warning_ignore("unsafe_call_argument") print(check(arg)) func check_recur() -> void: if recur != null: + @warning_ignore("unsafe_call_argument") print(check(recur)) else: recur = 1 @@ -22,11 +24,13 @@ func test() -> void: if prop == null: set('prop', 1) + @warning_ignore("unsafe_call_argument") print(check(prop)) set('prop', null) var loop = null while loop != 2: if loop != null: + @warning_ignore("unsafe_call_argument") print(check(loop)) loop = 1 if loop == null else 2 diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd new file mode 100644 index 0000000000..e1a1f07e47 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd @@ -0,0 +1,22 @@ +var _typed_array: Array[int] + +func weak_param_func(weak_param = _typed_array): + weak_param = [11] # Don't treat the literal as typed! + return weak_param + +func hard_param_func(hard_param := _typed_array): + hard_param = [12] + return hard_param + +func test(): + var weak_var = _typed_array + print(weak_var.is_typed()) + weak_var = [21] # Don't treat the literal as typed! + print(weak_var.is_typed()) + print(weak_param_func().is_typed()) + + var hard_var := _typed_array + print(hard_var.is_typed()) + hard_var = [22] + print(hard_var.is_typed()) + print(hard_param_func().is_typed()) diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out new file mode 100644 index 0000000000..34b18dbe7c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out @@ -0,0 +1,7 @@ +GDTEST_OK +true +false +false +true +true +true diff --git a/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd new file mode 100644 index 0000000000..a8641e4f3b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.gd @@ -0,0 +1,21 @@ +class BaseClass: + func _get_property_list(): + return {"property" : "definition"} + +class SuperClassMethodsRecognized extends BaseClass: + func _init(): + # Recognizes super class methods. + var _x = _get_property_list() + +class SuperMethodsRecognized extends BaseClass: + func _get_property_list(): + # Recognizes super method. + var result = super() + result["new"] = "new" + return result + +func test(): + var test1 = SuperClassMethodsRecognized.new() + print(test1._get_property_list()) # Calls base class's method. + var test2 = SuperMethodsRecognized.new() + print(test2._get_property_list()) diff --git a/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out new file mode 100644 index 0000000000..ccff660117 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/virtual_method_implemented.out @@ -0,0 +1,3 @@ +GDTEST_OK +{ "property": "definition" } +{ "property": "definition", "new": "new" } diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.gd b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.gd deleted file mode 100644 index 1b32491e48..0000000000 --- a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.gd +++ /dev/null @@ -1,4 +0,0 @@ -func test(): - var a: Array[Node] = [] - for node: Node in a: - print(node) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.out b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.out deleted file mode 100644 index 3b3fbd9bd1..0000000000 --- a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_equal_to_inferred.out +++ /dev/null @@ -1,5 +0,0 @@ -GDTEST_OK ->> WARNING ->> Line: 3 ->> REDUNDANT_FOR_VARIABLE_TYPE ->> The for loop iterator "node" already has inferred type "Node", the specified type is redundant. diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.gd b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.gd deleted file mode 100644 index fcbc13c53d..0000000000 --- a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.gd +++ /dev/null @@ -1,4 +0,0 @@ -func test(): - var a: Array[Node2D] = [] - for node: Node in a: - print(node) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.out b/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.out deleted file mode 100644 index 36d4a161d3..0000000000 --- a/modules/gdscript/tests/scripts/analyzer/warnings/for_loop_specified_type_is_supertype_of_inferred.out +++ /dev/null @@ -1,5 +0,0 @@ -GDTEST_OK ->> WARNING ->> Line: 3 ->> REDUNDANT_FOR_VARIABLE_TYPE ->> The for loop iterator "node" has inferred type "Node2D" but its supertype "Node" is specified. diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd index 849df0921e..c1776fe1b4 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd +++ b/modules/gdscript/tests/scripts/analyzer/warnings/get_node_without_onready.gd @@ -14,4 +14,5 @@ func test(): func do_add_node(): var node = Node.new() node.name = "Node" + @warning_ignore("unsafe_call_argument") add_child(node) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd index 29239a19da..939e787ea5 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd +++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.gd @@ -1,3 +1,5 @@ +class_name ShadowedClass + var member: int = 0 var print_debug := 'print_debug' @@ -12,5 +14,6 @@ func test(): var sqrt := 'sqrt' var member := 'member' var reference := 'reference' + var ShadowedClass := 'ShadowedClass' print('warn') diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out index accc791d8a..8297eed4b8 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out @@ -1,30 +1,34 @@ GDTEST_OK >> WARNING ->> Line: 3 +>> Line: 5 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "print_debug" has the same name as a built-in function. >> WARNING ->> Line: 9 +>> Line: 11 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "Array" has the same name as a built-in type. >> WARNING ->> Line: 10 +>> Line: 12 >> SHADOWED_GLOBAL_IDENTIFIER ->> The variable "Node" has the same name as a global class. +>> The variable "Node" has the same name as a native class. >> WARNING ->> Line: 11 +>> Line: 13 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "is_same" has the same name as a built-in function. >> WARNING ->> Line: 12 +>> Line: 14 >> SHADOWED_GLOBAL_IDENTIFIER >> The variable "sqrt" has the same name as a built-in function. >> WARNING ->> Line: 13 +>> Line: 15 >> SHADOWED_VARIABLE ->> The local variable "member" is shadowing an already-declared variable at line 1. +>> The local variable "member" is shadowing an already-declared variable at line 3. >> WARNING ->> Line: 14 +>> Line: 16 >> SHADOWED_VARIABLE_BASE_CLASS >> The local variable "reference" is shadowing an already-declared method at the base class "RefCounted". +>> WARNING +>> Line: 17 +>> SHADOWED_GLOBAL_IDENTIFIER +>> The variable "ShadowedClass" has the same name as a global class defined in "shadowning.gd". warn diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd new file mode 100644 index 0000000000..c6d9b37485 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd @@ -0,0 +1,54 @@ +func variant_func(x: Variant) -> void: + print(x) + +func int_func(x: int) -> void: + print(x) + +func float_func(x: float) -> void: + print(x) + +func node_func(x: Node) -> void: + print(x) + +# We don't want to execute it because of errors, just analyze. +func no_exec_test(): + var variant: Variant = null + var untyped_int = 42 + var untyped_string = "abc" + var variant_int: Variant = 42 + var variant_string: Variant = "abc" + var typed_int: int = 42 + + variant_func(untyped_int) # No warning. + variant_func(untyped_string) # No warning. + variant_func(variant_int) # No warning. + variant_func(variant_string) # No warning. + variant_func(typed_int) # No warning. + + int_func(untyped_int) + int_func(untyped_string) + int_func(variant_int) + int_func(variant_string) + int_func(typed_int) # No warning. + + float_func(untyped_int) + float_func(untyped_string) + float_func(variant_int) + float_func(variant_string) + float_func(typed_int) # No warning. + + node_func(variant) + node_func(Object.new()) + node_func(Node.new()) # No warning. + node_func(Node2D.new()) # No warning. + + # GH-82529 + print(Callable(self, "test")) # No warning. + print(Callable(variant, "test")) + + print(Dictionary(variant)) + print(Vector2(variant)) + print(int(variant)) + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out new file mode 100644 index 0000000000..3084515233 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out @@ -0,0 +1,57 @@ +GDTEST_OK +>> WARNING +>> Line: 28 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 29 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 30 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 31 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided. +>> WARNING +>> Line: 34 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. +>> WARNING +>> Line: 35 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. +>> WARNING +>> Line: 36 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. +>> WARNING +>> Line: 37 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided. +>> WARNING +>> Line: 40 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "node_func()" requires the subtype "Node" but the supertype "Variant" was provided. +>> WARNING +>> Line: 41 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the function "node_func()" requires the subtype "Node" but the supertype "Object" was provided. +>> WARNING +>> Line: 47 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the constructor "Callable()" requires the subtype "Object" but the supertype "Variant" was provided. +>> WARNING +>> Line: 49 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the constructor "Dictionary()" requires the subtype "Dictionary" but the supertype "Variant" was provided. +>> WARNING +>> Line: 50 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the constructor "Vector2()" requires the subtype "Vector2" or "Vector2i" but the supertype "Variant" was provided. +>> WARNING +>> Line: 51 +>> UNSAFE_CALL_ARGUMENT +>> The argument 1 of the constructor "int()" requires the subtype "int", "bool", or "float" but the supertype "Variant" was provided. diff --git a/modules/gdscript/tests/scripts/lsp/class.notest.gd b/modules/gdscript/tests/scripts/lsp/class.notest.gd new file mode 100644 index 0000000000..53d0b14d72 --- /dev/null +++ b/modules/gdscript/tests/scripts/lsp/class.notest.gd @@ -0,0 +1,132 @@ +extends Node + +class Inner1 extends Node: +# ^^^^^^ class1 -> class1 + var member1 := 42 + # ^^^^^^^ class1:member1 -> class1:member1 + var member2 : int = 13 + # ^^^^^^^ class1:member2 -> class1:member2 + var member3 = 1337 + # ^^^^^^^ class1:member3 -> class1:member3 + + signal changed(old, new) + # ^^^^^^^ class1:signal -> class1:signal + func my_func(arg1: int, arg2: String, arg3): + # | | | | | | ^^^^ class1:func:arg3 -> class1:func:arg3 + # | | | | ^^^^ class1:func:arg2 -> class1:func:arg2 + # | | ^^^^ class1:func:arg1 -> class1:func:arg1 + # ^^^^^^^ class1:func -> class1:func + print(arg1, arg2, arg3) + # | | | | ^^^^ -> class1:func:arg3 + # | | ^^^^ -> class1:func:arg2 + # ^^^^ -> class1:func:arg1 + changed.emit(arg1, arg3) + # | | | ^^^^ -> class1:func:arg3 + # | ^^^^ -> class1:func:arg1 + #<^^^^^ -> class1:signal + return arg1 + arg2.length() + arg3 + # | | | | ^^^^ -> class1:func:arg3 + # | | ^^^^ -> class1:func:arg2 + # ^^^^ -> class1:func:arg1 + +class Inner2: +# ^^^^^^ class2 -> class2 + var member1 := 42 + # ^^^^^^^ class2:member1 -> class2:member1 + var member2 : int = 13 + # ^^^^^^^ class2:member2 -> class2:member2 + var member3 = 1337 + # ^^^^^^^ class2:member3 -> class2:member3 + + signal changed(old, new) + # ^^^^^^^ class2:signal -> class2:signal + func my_func(arg1: int, arg2: String, arg3): + # | | | | | | ^^^^ class2:func:arg3 -> class2:func:arg3 + # | | | | ^^^^ class2:func:arg2 -> class2:func:arg2 + # | | ^^^^ class2:func:arg1 -> class2:func:arg1 + # ^^^^^^^ class2:func -> class2:func + print(arg1, arg2, arg3) + # | | | | ^^^^ -> class2:func:arg3 + # | | ^^^^ -> class2:func:arg2 + # ^^^^ -> class2:func:arg1 + changed.emit(arg1, arg3) + # | | | ^^^^ -> class2:func:arg3 + # | ^^^^ -> class2:func:arg1 + #<^^^^^ -> class2:signal + return arg1 + arg2.length() + arg3 + # | | | | ^^^^ -> class2:func:arg3 + # | | ^^^^ -> class2:func:arg2 + # ^^^^ -> class2:func:arg1 + +class Inner3 extends Inner2: +# | | ^^^^^^ -> class2 +# ^^^^^^ class3 -> class3 + var whatever = "foo" + # ^^^^^^^^ class3:whatever -> class3:whatever + + func _init(): + # ^^^^^ class3:init + # Note: no self-ref check here: resolves to `Object._init`. + # usages of `Inner3.new()` DO resolve to this `_init` + pass + + class NestedInInner3: + # ^^^^^^^^^^^^^^ class3:nested1 -> class3:nested1 + var some_value := 42 + # ^^^^^^^^^^ class3:nested1:some_value -> class3:nested1:some_value + + class AnotherNestedInInner3 extends NestedInInner3: + #! | | ^^^^^^^^^^^^^^ -> class3:nested1 + # ^^^^^^^^^^^^^^^^^^^^^ class3:nested2 -> class3:nested2 + var another_value := 13 + # ^^^^^^^^^^^^^ class3:nested2:another_value -> class3:nested2:another_value + +func _ready(): + var inner1 = Inner1.new() + # | | ^^^^^^ -> class1 + # ^^^^^^ func:class1 -> func:class1 + var value1 = inner1.my_func(1,"",3) + # | | | | ^^^^^^^ -> class1:func + # | | ^^^^^^ -> func:class1 + # ^^^^^^ func:class1:value1 -> func:class1:value1 + var value2 = inner1.member3 + # | | | | ^^^^^^^ -> class1:member3 + # | | ^^^^^^ -> func:class1 + # ^^^^^^ func:class1:value2 -> func:class1:value2 + print(value1, value2) + # | | ^^^^^^ -> func:class1:value2 + # ^^^^^^ -> func:class1:value1 + + var inner3 = Inner3.new() + # | | | | ^^^ -> class3:init + # | | ^^^^^^ -> class3 + # ^^^^^^ func:class3 -> func:class3 + print(inner3) + # ^^^^^^ -> func:class3 + + var nested1 = Inner3.NestedInInner3.new() + # | | | | ^^^^^^^^^^^^^^ -> class3:nested1 + # | | ^^^^^^ -> class3 + # ^^^^^^^ func:class3:nested1 -> func:class3:nested1 + var value_nested1 = nested1.some_value + # | | | | ^^^^^^^^^^ -> class3:nested1:some_value + # | | ^^^^^^^ -> func:class3:nested1 + # ^^^^^^^^^^^^^ func:class3:nested1:value + print(value_nested1) + # ^^^^^^^^^^^^^ -> func:class3:nested1:value + + var nested2 = Inner3.AnotherNestedInInner3.new() + # | | | | ^^^^^^^^^^^^^^^^^^^^^ -> class3:nested2 + # | | ^^^^^^ -> class3 + # ^^^^^^^ func:class3:nested2 -> func:class3:nested2 + var value_nested2 = nested2.some_value + # | | | | ^^^^^^^^^^ -> class3:nested1:some_value + # | | ^^^^^^^ -> func:class3:nested2 + # ^^^^^^^^^^^^^ func:class3:nested2:value + var another_value_nested2 = nested2.another_value + # | | | | ^^^^^^^^^^^^^ -> class3:nested2:another_value + # | | ^^^^^^^ -> func:class3:nested2 + # ^^^^^^^^^^^^^^^^^^^^^ func:class3:nested2:another_value_nested + print(value_nested2, another_value_nested2) + # | | ^^^^^^^^^^^^^^^^^^^^^ -> func:class3:nested2:another_value_nested + # ^^^^^^^^^^^^^ -> func:class3:nested2:value diff --git a/modules/gdscript/tests/scripts/lsp/enums.notest.gd b/modules/gdscript/tests/scripts/lsp/enums.notest.gd new file mode 100644 index 0000000000..38b9ec110a --- /dev/null +++ b/modules/gdscript/tests/scripts/lsp/enums.notest.gd @@ -0,0 +1,26 @@ +extends Node + +enum {UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY} +# | | | | ^^^^^^^^^ enum:unnamed:ally -> enum:unnamed:ally +# | | ^^^^^^^^^^ enum:unnamed:enemy -> enum:unnamed:enemy +# ^^^^^^^^^^^^ enum:unnamed:neutral -> enum:unnamed:neutral +enum Named {THING_1, THING_2, ANOTHER_THING = -1} +# | | | | | | ^^^^^^^^^^^^^ enum:named:thing3 -> enum:named:thing3 +# | | | | ^^^^^^^ enum:named:thing2 -> enum:named:thing2 +# | | ^^^^^^^ enum:named:thing1 -> enum:named:thing1 +# ^^^^^ enum:named -> enum:named + +func f(arg): + match arg: + UNIT_ENEMY: print(UNIT_ENEMY) + # | ^^^^^^^^^^ -> enum:unnamed:enemy + #<^^^^^^^^ -> enum:unnamed:enemy + Named.THING_2: print(Named.THING_2) + #! | | | | | ^^^^^^^ -> enum:named:thing2 + # | | | ^^^^^ -> enum:named + #! | ^^^^^^^ -> enum:named:thing2 + #<^^^ -> enum:named + _: print(UNIT_ENEMY, Named.ANOTHER_THING) + #! | | | | ^^^^^^^^^^^^^ -> enum:named:thing3 + # | | ^^^^^ -> enum:named + # ^^^^^^^^^^ -> enum:unnamed:enemy diff --git a/modules/gdscript/tests/scripts/lsp/indentation.notest.gd b/modules/gdscript/tests/scripts/lsp/indentation.notest.gd new file mode 100644 index 0000000000..c25d73a719 --- /dev/null +++ b/modules/gdscript/tests/scripts/lsp/indentation.notest.gd @@ -0,0 +1,28 @@ +extends Node + +var root = 0 +# ^^^^ 0_indent -> 0_indent + +func a(): + var alpha: int = root + 42 + # | | ^^^^ -> 0_indent + # ^^^^^ 1_indent -> 1_indent + if alpha > 42: + # ^^^^^ -> 1_indent + var beta := alpha + 13 + # | | ^^^^ -> 1_indent + # ^^^^ 2_indent -> 2_indent + if beta > alpha: + # | | ^^^^^ -> 1_indent + # ^^^^ -> 2_indent + var gamma = beta + 1 + # | | ^^^^ -> 2_indent + # ^^^^^ 3_indent -> 3_indent + print(gamma) + # ^^^^^ -> 3_indent + print(beta) + # ^^^^ -> 2_indent + print(alpha) + # ^^^^^ -> 1_indent + print(root) + # ^^^^ -> 0_indent diff --git a/modules/gdscript/tests/scripts/lsp/lambdas.notest.gd b/modules/gdscript/tests/scripts/lsp/lambdas.notest.gd new file mode 100644 index 0000000000..6f5d468eea --- /dev/null +++ b/modules/gdscript/tests/scripts/lsp/lambdas.notest.gd @@ -0,0 +1,91 @@ +extends Node + +var lambda_member1 := func(alpha: int, beta): return alpha + beta +# | | | | | | | | ^^^^ -> \1:beta +# | | | | | | ^^^^^ -> \1:alpha +# | | | | ^^^^ \1:beta -> \1:beta +# | | ^^^^^ \1:alpha -> \1:alpha +# ^^^^^^^^^^^^^^ \1 -> \1 + +var lambda_member2 := func(alpha, beta: int) -> int: +# | | | | | | +# | | | | | | +# | | | | ^^^^ \2:beta -> \2:beta +# | | ^^^^^ \2:alpha -> \2:alpha +# ^^^^^^^^^^^^^^ \2 -> \2 + return alpha + beta + # | | ^^^^ -> \2:beta + # ^^^^^ -> \2:alpha + +var lambda_member3 := func add_values(alpha, beta): return alpha + beta +# | | | | | | | | ^^^^ -> \3:beta +# | | | | | | ^^^^^ -> \3:alpha +# | | | | ^^^^ \3:beta -> \3:beta +# | | ^^^^^ \3:alpha -> \3:alpha +# ^^^^^^^^^^^^^^ \3 -> \3 + +var lambda_multiline = func(alpha: int, beta: int) -> int: +# | | | | | | +# | | | | | | +# | | | | ^^^^ \multi:beta -> \multi:beta +# | | ^^^^^ \multi:alpha -> \multi:alpha +# ^^^^^^^^^^^^^^^^ \multi -> \multi + print(alpha + beta) + # | | ^^^^ -> \multi:beta + # ^^^^^ -> \multi:alpha + var tmp = alpha + beta + 42 + # | | | | ^^^^ -> \multi:beta + # | | ^^^^^ -> \multi:alpha + # ^^^ \multi:tmp -> \multi:tmp + print(tmp) + # ^^^ -> \multi:tmp + if tmp > 50: + # ^^^ -> \multi:tmp + tmp += alpha + # | ^^^^^ -> \multi:alpha + #<^ -> \multi:tmp + else: + tmp -= beta + # | ^^^^ -> \multi:beta + #<^ -> \multi:tmp + print(tmp) + # ^^^ -> \multi:tmp + return beta + tmp + alpha + # | | | | ^^^^^ -> \multi:alpha + # | | ^^^ -> \multi:tmp + # ^^^^ -> \multi:beta + + +var some_name := "foo bar" +# ^^^^^^^^^ member:some_name -> member:some_name + +func _ready() -> void: + var a = lambda_member1.call(1,2) + # ^^^^^^^^^^^^^^ -> \1 + var b = lambda_member2.call(1,2) + # ^^^^^^^^^^^^^^ -> \2 + var c = lambda_member3.call(1,2) + # ^^^^^^^^^^^^^^ -> \3 + var d = lambda_multiline.call(1,2) + # ^^^^^^^^^^^^^^^^ -> \multi + print(a,b,c,d) + + var lambda_local = func(alpha, beta): return alpha + beta + # | | | | | | | | ^^^^ -> \local:beta + # | | | | | | ^^^^^ -> \local:alpha + # | | | | ^^^^ \local:beta -> \local:beta + # | | ^^^^^ \local:alpha -> \local:alpha + # ^^^^^^^^^^^^ \local -> \local + + var value := 42 + # ^^^^^ local:value -> local:value + var lambda_capture = func(): return value + some_name.length() + # | | | | ^^^^^^^^^ -> member:some_name + # | | ^^^^^ -> local:value + # ^^^^^^^^^^^^^^ \capture -> \capture + + var z = lambda_local.call(1,2) + # ^^^^^^^^^^^^ -> \local + var x = lambda_capture.call() + # ^^^^^^^^^^^^^^ -> \capture + print(z,x) diff --git a/modules/gdscript/tests/scripts/lsp/local_variables.notest.gd b/modules/gdscript/tests/scripts/lsp/local_variables.notest.gd new file mode 100644 index 0000000000..b6cc46f7da --- /dev/null +++ b/modules/gdscript/tests/scripts/lsp/local_variables.notest.gd @@ -0,0 +1,25 @@ +extends Node + +var member := 2 +# ^^^^^^ member -> member + +func test_member() -> void: + var test := member + 42 + # | | ^^^^^^ -> member + # ^^^^ test -> test + test += 3 + #<^^ -> test + member += 5 + #<^^^^ -> member + test = return_arg(test) + # | ^^^^ -> test + #<^^ -> test + print(test) + # ^^^^ -> test + +func return_arg(arg: int) -> int: +# ^^^ arg -> arg + arg += 2 + #<^ -> arg + return arg + # ^^^ -> arg
\ No newline at end of file diff --git a/modules/gdscript/tests/scripts/lsp/properties.notest.gd b/modules/gdscript/tests/scripts/lsp/properties.notest.gd new file mode 100644 index 0000000000..8dfaee2e5b --- /dev/null +++ b/modules/gdscript/tests/scripts/lsp/properties.notest.gd @@ -0,0 +1,65 @@ +extends Node + +var prop1 := 42 +# ^^^^^ prop1 -> prop1 +var prop2 : int = 42 +# ^^^^^ prop2 -> prop2 +var prop3 := 42: +# ^^^^^ prop3 -> prop3 + get: + return prop3 + 13 + # ^^^^^ -> prop3 + set(value): + # ^^^^^ prop3:value -> prop3:value + prop3 = value - 13 + # | ^^^^^ -> prop3:value + #<^^^ -> prop3 +var prop4: int: +# ^^^^^ prop4 -> prop4 + get: + return 42 +var prop5 := 42: +# ^^^^^ prop5 -> prop5 + set(value): + # ^^^^^ prop5:value -> prop5:value + prop5 = value - 13 + # | ^^^^^ -> prop5:value + #<^^^ -> prop5 + +var prop6: +# ^^^^^ prop6 -> prop6 + get = get_prop6, + # ^^^^^^^^^ -> get_prop6 + set = set_prop6 + # ^^^^^^^^^ -> set_prop6 +func get_prop6(): +# ^^^^^^^^^ get_prop6 -> get_prop6 + return 42 +func set_prop6(value): +# | | ^^^^^ set_prop6:value -> set_prop6:value +# ^^^^^^^^^ set_prop6 -> set_prop6 + print(value) + # ^^^^^ -> set_prop6:value + +var prop7: +# ^^^^^ prop7 -> prop7 + get = get_prop7 + # ^^^^^^^^^ -> get_prop7 +func get_prop7(): +# ^^^^^^^^^ get_prop7 -> get_prop7 + return 42 + +var prop8: +# ^^^^^ prop8 -> prop8 + set = set_prop8 + # ^^^^^^^^^ -> set_prop8 +func set_prop8(value): +# | | ^^^^^ set_prop8:value -> set_prop8:value +# ^^^^^^^^^ set_prop8 -> set_prop8 + print(value) + # ^^^^^ -> set_prop8:value + +const const_var := 42 +# ^^^^^^^^^ const_var -> const_var +static var static_var := 42 +# ^^^^^^^^^^ static_var -> static_var diff --git a/modules/gdscript/tests/scripts/lsp/scopes.notest.gd b/modules/gdscript/tests/scripts/lsp/scopes.notest.gd new file mode 100644 index 0000000000..20b8fb9bd7 --- /dev/null +++ b/modules/gdscript/tests/scripts/lsp/scopes.notest.gd @@ -0,0 +1,106 @@ +extends Node + +var member := 2 +# ^^^^^^ public -> public + +signal some_changed(new_value) +# | | ^^^^^^^^^ signal:parameter -> signal:parameter +# ^^^^^^^^^^^^ signal -> signal +var some_value := 42: +# ^^^^^^^^^^ property -> property + get: + return some_value + # ^^^^^^^^^^ -> property + set(value): + # ^^^^^ property:set:value -> property:set:value + some_changed.emit(value) + # | ^^^^^ -> property:set:value + #<^^^^^^^^^^ -> signal + some_value = value + # | ^^^^^ -> property:set:value + #<^^^^^^^^ -> property + +func v(): + var value := member + 2 + # | | ^^^^^^ -> public + # ^^^^^ v:value -> v:value + print(value) + # ^^^^^ -> v:value + if value > 0: + # ^^^^^ -> v:value + var beta := value + 2 + # | | ^^^^^ -> v:value + # ^^^^ v:if:beta -> v:if:beta + print(beta) + # ^^^^ -> v:if:beta + + for counter in beta: + # | | ^^^^ -> v:if:beta + # ^^^^^^^ v:if:counter -> v:if:counter + print (counter) + # ^^^^^^^ -> v:if:counter + + else: + for counter in value: + # | | ^^^^^ -> v:value + # ^^^^^^^ v:else:counter -> v:else:counter + print(counter) + # ^^^^^^^ -> v:else:counter + +func f(): + var func1 = func(value): print(value + 13) + # | | | | ^^^^^ -> f:func1:value + # | | ^^^^^ f:func1:value -> f:func1:value + # ^^^^^ f:func1 -> f:func1 + var func2 = func(value): print(value + 42) + # | | | | ^^^^^ -> f:func2:value + # | | ^^^^^ f:func2:value -> f:func2:value + # ^^^^^ f:func2 -> f:func2 + + func1.call(1) + #<^^^ -> f:func1 + func2.call(2) + #<^^^ -> f:func2 + +func m(): + var value = 42 + # ^^^^^ m:value -> m:value + + match value: + # ^^^^^ -> m:value + 13: + print(value) + # ^^^^^ -> m:value + [var start, _, var end]: + # | | ^^^ m:match:array:end -> m:match:array:end + # ^^^^^ m:match:array:start -> m:match:array:start + print(start + end) + # | | ^^^ -> m:match:array:end + # ^^^^^ -> m:match:array:start + { "name": var name }: + # ^^^^ m:match:dict:var -> m:match:dict:var + print(name) + # ^^^^ -> m:match:dict:var + var whatever: + # ^^^^^^^^ m:match:var -> m:match:var + print(whatever) + # ^^^^^^^^ -> m:match:var + +func m2(): + var value = 42 + # ^^^^^ m2:value -> m2:value + + match value: + # ^^^^^ -> m2:value + { "name": var name }: + # ^^^^ m2:match:dict:var -> m2:match:dict:var + print(name) + # ^^^^ -> m2:match:dict:var + [var name, ..]: + # ^^^^ m2:match:array:var -> m2:match:array:var + print(name) + # ^^^^ -> m2:match:array:var + var name: + # ^^^^ m2:match:var -> m2:match:var + print(name) + # ^^^^ -> m2:match:var diff --git a/modules/gdscript/tests/scripts/lsp/shadowing_initializer.notest.gd b/modules/gdscript/tests/scripts/lsp/shadowing_initializer.notest.gd new file mode 100644 index 0000000000..338000fa0e --- /dev/null +++ b/modules/gdscript/tests/scripts/lsp/shadowing_initializer.notest.gd @@ -0,0 +1,56 @@ +extends Node + +var value := 42 +# ^^^^^ member:value -> member:value + +func variable(): + var value = value + 42 + #! | | ^^^^^ -> member:value + # ^^^^^ variable:value -> variable:value + print(value) + # ^^^^^ -> variable:value + +func array(): + var value = [1,value,3,value+4] + #! | | | | ^^^^^ -> member:value + #! | | ^^^^^ -> member:value + # ^^^^^ array:value -> array:value + print(value) + # ^^^^^ -> array:value + +func dictionary(): + var value = { + # ^^^^^ dictionary:value -> dictionary:value + "key1": value, + #! ^^^^^ -> member:value + "key2": 1 + value + 3, + #! ^^^^^ -> member:value + } + print(value) + # ^^^^^ -> dictionary:value + +func for_loop(): + for value in value: + # | | ^^^^^ -> member:value + # ^^^^^ for:value -> for:value + print(value) + # ^^^^^ -> for:value + +func for_range(): + for value in range(5, value): + # | | ^^^^^ -> member:value + # ^^^^^ for:range:value -> for:range:value + print(value) + # ^^^^^ -> for:range:value + +func matching(): + match value: + # ^^^^^ -> member:value + 42: print(value) + # ^^^^^ -> member:value + [var value, ..]: print(value) + # | | ^^^^^ -> match:array:value + # ^^^^^ match:array:value -> match:array:value + var value: print(value) + # | | ^^^^^ -> match:var:value + # ^^^^^ match:var:value -> match:var:value diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd new file mode 100644 index 0000000000..e5eecbb819 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd @@ -0,0 +1,2 @@ +func test(): + print(r"\") diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out new file mode 100644 index 0000000000..c8e843b0d7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Unterminated string. diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd new file mode 100644 index 0000000000..9168b69f86 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd @@ -0,0 +1,2 @@ +func test(): + print(r"\\"") diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out new file mode 100644 index 0000000000..c8e843b0d7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Unterminated string. diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd new file mode 100644 index 0000000000..37dc910e5f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd @@ -0,0 +1,3 @@ +func test(): + # v + print(r"['"]*") diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out new file mode 100644 index 0000000000..dcb5c2f289 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Closing "]" doesn't have an opening counterpart. diff --git a/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.gd b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.gd new file mode 100644 index 0000000000..d88b4a37c4 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.gd @@ -0,0 +1,5 @@ +func test(): + var a = 0 + match a: + 0 when a = 1: + print("assignment not allowed on pattern guard") diff --git a/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.out b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.out new file mode 100644 index 0000000000..e8f9130706 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/match_guard_with_assignment.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Assignment is not allowed inside an expression. diff --git a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd index 7e1982597c..0c8a5d1367 100644 --- a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd +++ b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd @@ -14,3 +14,7 @@ func test(): var TAU = "TAU" print(TAU) + + # New keyword for pattern guards. + var when = "when" + print(when) diff --git a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out index aae2ae13d5..8ac8e92ef7 100644 --- a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out +++ b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out @@ -4,3 +4,4 @@ PI INF NAN TAU +when diff --git a/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd b/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd index f04f4de08d..19f6e08285 100644 --- a/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd +++ b/modules/gdscript/tests/scripts/parser/features/dollar_and_percent_get_node.gd @@ -3,27 +3,32 @@ extends Node func test(): var child = Node.new() child.name = "Child" + @warning_ignore("unsafe_call_argument") add_child(child) child.owner = self var hey = Node.new() hey.name = "Hey" + @warning_ignore("unsafe_call_argument") child.add_child(hey) hey.owner = self hey.unique_name_in_owner = true var fake_hey = Node.new() fake_hey.name = "Hey" + @warning_ignore("unsafe_call_argument") add_child(fake_hey) fake_hey.owner = self var sub_child = Node.new() sub_child.name = "SubChild" + @warning_ignore("unsafe_call_argument") hey.add_child(sub_child) sub_child.owner = self var howdy = Node.new() howdy.name = "Howdy" + @warning_ignore("unsafe_call_argument") sub_child.add_child(howdy) howdy.owner = self howdy.unique_name_in_owner = true diff --git a/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd index 8ba558e91d..3d9404b20b 100644 --- a/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd +++ b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd @@ -5,9 +5,11 @@ func test(): # Create the required node structure. var hello = Node.new() hello.name = "Hello" + @warning_ignore("unsafe_call_argument") add_child(hello) var world = Node.new() world.name = "World" + @warning_ignore("unsafe_call_argument") hello.add_child(world) # All the ways of writing node paths below with the `$` operator are valid. diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd index acf9ff2e21..c9d05a7e68 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd +++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd @@ -1,3 +1,5 @@ +extends Node + @export var example = 99 @export_range(0, 100) var example_range = 100 @export_range(0, 100, 1) var example_range_step = 101 @@ -6,7 +8,8 @@ @export var color: Color @export_color_no_alpha var color_no_alpha: Color @export_node_path("Sprite2D", "Sprite3D", "Control", "Node") var nodepath := ^"hello" - +@export var node: Node +@export var node_array: Array[Node] func test(): print(example) @@ -16,3 +19,5 @@ func test(): print(color) print(color_no_alpha) print(nodepath) + print(node) + print(var_to_str(node_array)) diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.out b/modules/gdscript/tests/scripts/parser/features/export_variable.out index bae35e75c6..5430c975f4 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_variable.out +++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out @@ -6,3 +6,5 @@ GDTEST_OK (0, 0, 0, 1) (0, 0, 0, 1) hello +<null> +Array[Node]([]) diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd b/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd index df6001c7e2..f16c768f7f 100644 --- a/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd +++ b/modules/gdscript/tests/scripts/parser/features/lambda_ends_with_new_line.gd @@ -26,6 +26,7 @@ func test(): if true: (v as Callable).call() print() + @warning_ignore("unsafe_call_argument") other(v) print() diff --git a/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd index 59cdc7d6c2..31de73813f 100644 --- a/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd +++ b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd @@ -2,4 +2,5 @@ func foo(x): return x + 1 func test(): + @warning_ignore("unsafe_call_argument") print(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(foo(0))))))))))))))))))))))))) diff --git a/modules/gdscript/tests/scripts/parser/features/r_strings.gd b/modules/gdscript/tests/scripts/parser/features/r_strings.gd new file mode 100644 index 0000000000..6f546f28be --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/r_strings.gd @@ -0,0 +1,22 @@ +func test(): + print(r"test ' \' \" \\ \n \t \u2023 test") + print(r"\n\\[\t ]*(\w+)") + print(r"") + print(r"\"") + print(r"\\\"") + print(r"\\") + print(r"\" \\\" \\\\\"") + print(r"\ \\ \\\ \\\\ \\\\\ \\") + print(r'"') + print(r'"(?:\\.|[^"])*"') + print(r"""""") + print(r"""test \t "test"="" " \" \\\" \ \\ \\\ test""") + print(r'''r"""test \t "test"="" " \" \\\" \ \\ \\\ test"""''') + print(r"\t + \t") + print(r"\t \ + \t") + print(r"""\t + \t""") + print(r"""\t \ + \t""") diff --git a/modules/gdscript/tests/scripts/parser/features/r_strings.out b/modules/gdscript/tests/scripts/parser/features/r_strings.out new file mode 100644 index 0000000000..114ef0a6c3 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/r_strings.out @@ -0,0 +1,22 @@ +GDTEST_OK +test ' \' \" \\ \n \t \u2023 test +\n\\[\t ]*(\w+) + +\" +\\\" +\\ +\" \\\" \\\\\" +\ \\ \\\ \\\\ \\\\\ \\ +" +"(?:\\.|[^"])*" + +test \t "test"="" " \" \\\" \ \\ \\\ test +r"""test \t "test"="" " \" \\\" \ \\ \\\ test""" +\t + \t +\t \ + \t +\t + \t +\t \ + \t diff --git a/modules/gdscript/tests/scripts/parser/features/super.gd b/modules/gdscript/tests/scripts/parser/features/super.gd index f5ae2a74a7..33accd92a9 100644 --- a/modules/gdscript/tests/scripts/parser/features/super.gd +++ b/modules/gdscript/tests/scripts/parser/features/super.gd @@ -36,6 +36,7 @@ class SayNothing extends Say: print("howdy, see above") func say(name): + @warning_ignore("unsafe_call_argument") super(name + " super'd") print(prefix, " say nothing... or not? ", name) diff --git a/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd index 523959a016..20cc0cee2e 100644 --- a/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd +++ b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd @@ -29,6 +29,7 @@ func test(): const d = 1.1 _process(d) + @warning_ignore("unsafe_call_argument") print(is_equal_approx(ㄥ, PI + (d * PI))) func _process(Δ: float) -> void: diff --git a/modules/gdscript/tests/scripts/runtime/errors/gd_utility_function_wrong_arg.gd b/modules/gdscript/tests/scripts/runtime/errors/gd_utility_function_wrong_arg.gd new file mode 100644 index 0000000000..340fc8c150 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/gd_utility_function_wrong_arg.gd @@ -0,0 +1,3 @@ +func test(): + var x = Color() + print(len(x)) # GDScript utility function. diff --git a/modules/gdscript/tests/scripts/runtime/errors/gd_utility_function_wrong_arg.out b/modules/gdscript/tests/scripts/runtime/errors/gd_utility_function_wrong_arg.out new file mode 100644 index 0000000000..6d2938dcf3 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/gd_utility_function_wrong_arg.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/gd_utility_function_wrong_arg.gd +>> 3 +>> Error calling GDScript utility function "len()": Value of type 'Color' can't provide a length. diff --git a/modules/gdscript/tests/scripts/runtime/errors/utility_function_wrong_arg.gd b/modules/gdscript/tests/scripts/runtime/errors/utility_function_wrong_arg.gd new file mode 100644 index 0000000000..6568155bae --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/utility_function_wrong_arg.gd @@ -0,0 +1,3 @@ +func test(): + var x = Color() + print(floor(x)) # Built-in utility function. diff --git a/modules/gdscript/tests/scripts/runtime/errors/utility_function_wrong_arg.out b/modules/gdscript/tests/scripts/runtime/errors/utility_function_wrong_arg.out new file mode 100644 index 0000000000..b311bfa38a --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/utility_function_wrong_arg.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/utility_function_wrong_arg.gd +>> 3 +>> Error calling utility function "floor()": Argument "x" must be "int", "float", "Vector2", "Vector2i", "Vector3", "Vector3i", "Vector4", or "Vector4i". diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd index 58b4df5a79..bc899a3a6f 100644 --- a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd +++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd @@ -21,6 +21,12 @@ func test(): var elem := e prints(var_to_str(e), var_to_str(elem)) + # GH-82021 + print("Test implicitly typed array literal.") + for e: float in [100, 200, 300]: + var elem := e + prints(var_to_str(e), var_to_str(elem)) + print("Test String-keys dictionary.") var d1 := {a = 1, b = 2, c = 3} for k: StringName in d1: diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out index f67f7d89d5..eeebdc4be5 100644 --- a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out +++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out @@ -15,6 +15,10 @@ Test typed int array. 10.0 10.0 20.0 20.0 30.0 30.0 +Test implicitly typed array literal. +100.0 100.0 +200.0 200.0 +300.0 300.0 Test String-keys dictionary. &"a" &"a" &"b" &"b" diff --git a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd new file mode 100644 index 0000000000..4cb51f8512 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd @@ -0,0 +1,71 @@ +var global := 0 + +func test(): + var a = 0 + var b = 1 + + match a: + 0 when b == 0: + print("does not run" if true else "") + 0 when b == 1: + print("guards work") + _: + print("does not run") + + match a: + var a_bind when b == 0: + prints("a is", a_bind, "and b is 0") + var a_bind when b == 1: + prints("a is", a_bind, "and b is 1") + _: + print("does not run") + + match a: + var a_bind when a_bind < 0: + print("a is less than zero") + var a_bind when a_bind == 0: + print("a is equal to zero") + _: + print("a is more than zero") + + match [1, 2, 3]: + [1, 2, var element] when element == 0: + print("does not run") + [1, 2, var element] when element == 3: + print("3rd element is 3") + + match a: + _ when b == 0: + print("does not run") + _ when b == 1: + print("works with wildcard too.") + _: + print("does not run") + + match a: + 0, 1 when b == 0: + print("does not run") + 0, 1 when b == 1: + print("guard with multiple patterns") + _: + print("does not run") + + match a: + 0 when b == 0: + print("does not run") + 0: + print("regular pattern after guard mismatch") + + match a: + 1 when side_effect(): + print("should not run the side effect call") + 0 when side_effect(): + print("will run the side effect call, but not this") + _: + assert(global == 1) + print("side effect only ran once") + +func side_effect(): + print("side effect") + global += 1 + return false diff --git a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.out b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.out new file mode 100644 index 0000000000..452d1ff4bf --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.out @@ -0,0 +1,10 @@ +GDTEST_OK +guards work +a is 0 and b is 1 +a is equal to zero +3rd element is 3 +works with wildcard too. +guard with multiple patterns +regular pattern after guard mismatch +side effect +side effect only ran once diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.gd b/modules/gdscript/tests/scripts/runtime/features/member_info.gd new file mode 100644 index 0000000000..805ea42455 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/member_info.gd @@ -0,0 +1,72 @@ +class_name TestMemberInfo + +class MyClass: + pass + +enum MyEnum {} + +const Utils = preload("../../utils.notest.gd") + +static var test_static_var_untyped +static var test_static_var_weak_null = null +static var test_static_var_weak_int = 1 +static var test_static_var_hard_int: int + +var test_var_untyped +var test_var_weak_null = null +var test_var_weak_int = 1 +@export var test_var_weak_int_exported = 1 +var test_var_weak_variant_type = TYPE_NIL +@export var test_var_weak_variant_type_exported = TYPE_NIL +var test_var_hard_variant: Variant +var test_var_hard_int: int +var test_var_hard_variant_type: Variant.Type +@export var test_var_hard_variant_type_exported: Variant.Type +var test_var_hard_node_process_mode: Node.ProcessMode +var test_var_hard_my_enum: MyEnum +var test_var_hard_array: Array +var test_var_hard_array_int: Array[int] +var test_var_hard_array_variant_type: Array[Variant.Type] +var test_var_hard_array_node_process_mode: Array[Node.ProcessMode] +var test_var_hard_array_my_enum: Array[MyEnum] +var test_var_hard_array_resource: Array[Resource] +var test_var_hard_array_this: Array[TestMemberInfo] +var test_var_hard_array_my_class: Array[MyClass] +var test_var_hard_resource: Resource +var test_var_hard_this: TestMemberInfo +var test_var_hard_my_class: MyClass + +static func test_static_func(): pass + +func test_func_implicit_void(): pass +func test_func_explicit_void() -> void: pass +func test_func_weak_null(): return null +func test_func_weak_int(): return 1 +func test_func_hard_variant() -> Variant: return null +func test_func_hard_int() -> int: return 1 +func test_func_args_1(_a: int, _b: Array[int], _c: int = 1, _d = 2): pass +func test_func_args_2(_a = 1, _b = _a, _c = [2], _d = 3): pass + +signal test_signal_1() +signal test_signal_2(a: Variant, b) +signal test_signal_3(a: int, b: Array[int]) +signal test_signal_4(a: Variant.Type, b: Array[Variant.Type]) +signal test_signal_5(a: MyEnum, b: Array[MyEnum]) +signal test_signal_6(a: Resource, b: Array[Resource]) +signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo]) +signal test_signal_8(a: MyClass, b: Array[MyClass]) + +func test(): + var script: Script = get_script() + for property in script.get_property_list(): + if str(property.name).begins_with("test_"): + print(Utils.get_property_signature(property, true)) + for property in get_property_list(): + if str(property.name).begins_with("test_"): + print(Utils.get_property_signature(property)) + for method in get_method_list(): + if str(method.name).begins_with("test_"): + print(Utils.get_method_signature(method)) + for method in get_signal_list(): + if str(method.name).begins_with("test_"): + print(Utils.get_method_signature(method, true)) diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.out b/modules/gdscript/tests/scripts/runtime/features/member_info.out new file mode 100644 index 0000000000..3a91507da9 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/member_info.out @@ -0,0 +1,45 @@ +GDTEST_OK +static var test_static_var_untyped: Variant +static var test_static_var_weak_null: Variant +static var test_static_var_weak_int: Variant +static var test_static_var_hard_int: int +var test_var_untyped: Variant +var test_var_weak_null: Variant +var test_var_weak_int: Variant +@export var test_var_weak_int_exported: int +var test_var_weak_variant_type: Variant +@export var test_var_weak_variant_type_exported: Variant.Type +var test_var_hard_variant: Variant +var test_var_hard_int: int +var test_var_hard_variant_type: Variant.Type +@export var test_var_hard_variant_type_exported: Variant.Type +var test_var_hard_node_process_mode: Node.ProcessMode +var test_var_hard_my_enum: TestMemberInfo.MyEnum +var test_var_hard_array: Array +var test_var_hard_array_int: Array[int] +var test_var_hard_array_variant_type: Array[Variant.Type] +var test_var_hard_array_node_process_mode: Array[Node.ProcessMode] +var test_var_hard_array_my_enum: Array[TestMemberInfo.MyEnum] +var test_var_hard_array_resource: Array[Resource] +var test_var_hard_array_this: Array[TestMemberInfo] +var test_var_hard_array_my_class: Array[RefCounted] +var test_var_hard_resource: Resource +var test_var_hard_this: TestMemberInfo +var test_var_hard_my_class: RefCounted +static func test_static_func() -> void +func test_func_implicit_void() -> void +func test_func_explicit_void() -> void +func test_func_weak_null() -> Variant +func test_func_weak_int() -> Variant +func test_func_hard_variant() -> Variant +func test_func_hard_int() -> int +func test_func_args_1(_a: int, _b: Array[int], _c: int = 1, _d: Variant = 2) -> void +func test_func_args_2(_a: Variant = 1, _b: Variant = null, _c: Variant = null, _d: Variant = 3) -> void +signal test_signal_1() +signal test_signal_2(a: Variant, b: Variant) +signal test_signal_3(a: int, b: Array[int]) +signal test_signal_4(a: Variant.Type, b: Array[Variant.Type]) +signal test_signal_5(a: TestMemberInfo.MyEnum, b: Array[TestMemberInfo.MyEnum]) +signal test_signal_6(a: Resource, b: Array[Resource]) +signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo]) +signal test_signal_8(a: RefCounted, b: Array[RefCounted]) diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd new file mode 100644 index 0000000000..d0cbeeab85 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd @@ -0,0 +1,45 @@ +# GH-82169 + +const Utils = preload("../../utils.notest.gd") + +class A: + static var test_static_var_a1 + static var test_static_var_a2 + var test_var_a1 + var test_var_a2 + static func test_static_func_a1(): pass + static func test_static_func_a2(): pass + func test_func_a1(): pass + func test_func_a2(): pass + signal test_signal_a1() + signal test_signal_a2() + +class B extends A: + static var test_static_var_b1 + static var test_static_var_b2 + var test_var_b1 + var test_var_b2 + static func test_static_func_b1(): pass + static func test_static_func_b2(): pass + func test_func_b1(): pass + func test_func_b2(): pass + signal test_signal_b1() + signal test_signal_b2() + +func test(): + var b := B.new() + for property in (B as GDScript).get_property_list(): + if str(property.name).begins_with("test_"): + print(Utils.get_property_signature(property, true)) + print("---") + for property in b.get_property_list(): + if str(property.name).begins_with("test_"): + print(Utils.get_property_signature(property)) + print("---") + for method in b.get_method_list(): + if str(method.name).begins_with("test_"): + print(Utils.get_method_signature(method)) + print("---") + for method in b.get_signal_list(): + if str(method.name).begins_with("test_"): + print(Utils.get_method_signature(method, true)) diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.out b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.out new file mode 100644 index 0000000000..f294b66ef9 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.out @@ -0,0 +1,24 @@ +GDTEST_OK +static var test_static_var_a1: Variant +static var test_static_var_a2: Variant +static var test_static_var_b1: Variant +static var test_static_var_b2: Variant +--- +var test_var_b1: Variant +var test_var_b2: Variant +var test_var_a1: Variant +var test_var_a2: Variant +--- +static func test_static_func_b1() -> void +static func test_static_func_b2() -> void +func test_func_b1() -> void +func test_func_b2() -> void +static func test_static_func_a1() -> void +static func test_static_func_a2() -> void +func test_func_a1() -> void +func test_func_a2() -> void +--- +signal test_signal_b1() +signal test_signal_b2() +signal test_signal_a1() +signal test_signal_a2() diff --git a/modules/gdscript/tests/scripts/runtime/features/metatypes.gd b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd new file mode 100644 index 0000000000..6c5df32ffe --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd @@ -0,0 +1,36 @@ +class MyClass: + const TEST = 10 + +enum MyEnum {A, B, C} + +const Utils = preload("../../utils.notest.gd") +const Other = preload("./metatypes.notest.gd") + +var test_native := JSON +var test_script := Other +var test_class := MyClass +var test_enum := MyEnum + +func check_gdscript_native_class(value: Variant) -> void: + print(var_to_str(value).get_slice(",", 0).trim_prefix("Object(")) + +func check_gdscript(value: GDScript) -> void: + print(value.get_class()) + +func check_enum(value: Dictionary) -> void: + print(value) + +func test(): + for property in get_property_list(): + if str(property.name).begins_with("test_"): + print(Utils.get_property_signature(property)) + + check_gdscript_native_class(test_native) + check_gdscript(test_script) + check_gdscript(test_class) + check_enum(test_enum) + + print(test_native.stringify([])) + print(test_script.TEST) + print(test_class.TEST) + print(test_enum.keys()) diff --git a/modules/gdscript/tests/scripts/runtime/features/metatypes.notest.gd b/modules/gdscript/tests/scripts/runtime/features/metatypes.notest.gd new file mode 100644 index 0000000000..e6a591b927 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/metatypes.notest.gd @@ -0,0 +1 @@ +const TEST = 100 diff --git a/modules/gdscript/tests/scripts/runtime/features/metatypes.out b/modules/gdscript/tests/scripts/runtime/features/metatypes.out new file mode 100644 index 0000000000..352d1caa59 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/metatypes.out @@ -0,0 +1,13 @@ +GDTEST_OK +var test_native: GDScriptNativeClass +var test_script: GDScript +var test_class: GDScript +var test_enum: Dictionary +GDScriptNativeClass +GDScript +GDScript +{ "A": 0, "B": 1, "C": 2 } +[] +100 +10 +["A", "B", "C"] diff --git a/modules/gdscript/tests/scripts/runtime/features/object_constructor.gd b/modules/gdscript/tests/scripts/runtime/features/object_constructor.gd new file mode 100644 index 0000000000..b875efef56 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/object_constructor.gd @@ -0,0 +1,6 @@ +# GH-73213 + +func test(): + var object := Object.new() # Not `Object()`. + print(object.get_class()) + object.free() diff --git a/modules/gdscript/tests/scripts/runtime/features/object_constructor.out b/modules/gdscript/tests/scripts/runtime/features/object_constructor.out new file mode 100644 index 0000000000..3673881576 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/object_constructor.out @@ -0,0 +1,2 @@ +GDTEST_OK +Object diff --git a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd index 2f55059334..691b611574 100644 --- a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd +++ b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd @@ -7,11 +7,12 @@ func test(): test_builtin_call_validated(Vector2.UP, false) test_object_call(RefCounted.new(), false) test_object_call_method_bind(Resource.new(), false) - test_object_call_ptrcall(RefCounted.new(), false) + test_object_call_method_bind_validated(RefCounted.new(), false) print("end") func test_construct(v, f): + @warning_ignore("unsafe_call_argument") Vector2(v, v) # Built-in type construct. assert(not f) # Test unary operator reading from `nil`. @@ -39,7 +40,7 @@ func test_object_call_method_bind(v: Resource, f): v.duplicate() # Native type method call with MethodBind. assert(not f) # Test unary operator reading from `nil`. -func test_object_call_ptrcall(v: RefCounted, f): +func test_object_call_method_bind_validated(v: RefCounted, f): @warning_ignore("return_value_discarded") - v.get_reference_count() # Native type method call with ptrcall. + v.get_reference_count() # Native type method call with validated MethodBind. assert(not f) # Test unary operator reading from `nil`. diff --git a/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.gd b/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.gd new file mode 100644 index 0000000000..f6aa58737f --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.gd @@ -0,0 +1,12 @@ +# GH-79521 + +class_name TestStaticMethodAsCallable + +static func static_func() -> String: + return "Test" + +func test(): + var a: Callable = TestStaticMethodAsCallable.static_func + var b: Callable = static_func + prints(a.call(), a.is_valid()) + prints(b.call(), b.is_valid()) diff --git a/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.out b/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.out new file mode 100644 index 0000000000..e6d461b8f9 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/static_method_as_callable.out @@ -0,0 +1,3 @@ +GDTEST_OK +Test true +Test true diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd index 8da8bb7e53..7fa76ca4b0 100644 --- a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd +++ b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd @@ -44,6 +44,7 @@ func test(): @warning_ignore("unsafe_method_access") var path = get_script().get_path().get_base_dir() + @warning_ignore("unsafe_call_argument") var other = load(path + "/static_variables_load.gd") prints("load.perm:", other.perm) diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.gd b/modules/gdscript/tests/scripts/runtime/features/stringify.gd index fead2df854..0dbb252b0e 100644 --- a/modules/gdscript/tests/scripts/runtime/features/stringify.gd +++ b/modules/gdscript/tests/scripts/runtime/features/stringify.gd @@ -24,7 +24,7 @@ func test(): print(StringName("hello")) print(NodePath("hello/world")) var node := Node.new() - print(RID(node)) + print(RID(node)) # TODO: Why is the constructor (or implicit cast) not documented? print(node.get_name) print(node.property_list_changed) node.free() diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd new file mode 100644 index 0000000000..781843b8e2 --- /dev/null +++ b/modules/gdscript/tests/scripts/utils.notest.gd @@ -0,0 +1,199 @@ +static func get_type(property: Dictionary, is_return: bool = false) -> String: + match property.type: + TYPE_NIL: + if property.usage & PROPERTY_USAGE_NIL_IS_VARIANT: + return "Variant" + return "void" if is_return else "null" + TYPE_INT: + if property.usage & PROPERTY_USAGE_CLASS_IS_ENUM: + if property.class_name == &"": + return "<unknown enum>" + return property.class_name + TYPE_ARRAY: + if property.hint == PROPERTY_HINT_ARRAY_TYPE: + if str(property.hint_string).is_empty(): + return "Array[<unknown type>]" + return "Array[%s]" % property.hint_string + TYPE_OBJECT: + if not str(property.class_name).is_empty(): + return property.class_name + return type_string(property.type) + + +static func get_property_signature(property: Dictionary, is_static: bool = false) -> String: + var result: String = "" + if not (property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE): + printerr("Missing `PROPERTY_USAGE_SCRIPT_VARIABLE` flag.") + if property.usage & PROPERTY_USAGE_DEFAULT: + result += "@export " + if is_static: + result += "static " + result += "var " + property.name + ": " + get_type(property) + return result + + +static func get_property_additional_info(property: Dictionary) -> String: + return 'hint=%s hint_string="%s" usage=%s' % [ + get_property_hint_name(property.hint).trim_prefix("PROPERTY_HINT_"), + str(property.hint_string).c_escape(), + get_property_usage_string(property.usage).replace("PROPERTY_USAGE_", ""), + ] + + +static func get_method_signature(method: Dictionary, is_signal: bool = false) -> String: + var result: String = "" + if method.flags & METHOD_FLAG_STATIC: + result += "static " + result += ("signal " if is_signal else "func ") + method.name + "(" + + var args: Array[Dictionary] = method.args + var default_args: Array = method.default_args + var mandatory_argc: int = args.size() - default_args.size() + for i in args.size(): + if i > 0: + result += ", " + var arg: Dictionary = args[i] + result += arg.name + ": " + get_type(arg) + if i >= mandatory_argc: + result += " = " + var_to_str(default_args[i - mandatory_argc]) + + result += ")" + if is_signal: + if get_type(method.return, true) != "void": + printerr("Signal return type must be `void`.") + else: + result += " -> " + get_type(method.return, true) + return result + + +static func get_property_hint_name(hint: PropertyHint) -> String: + match hint: + PROPERTY_HINT_NONE: + return "PROPERTY_HINT_NONE" + PROPERTY_HINT_RANGE: + return "PROPERTY_HINT_RANGE" + PROPERTY_HINT_ENUM: + return "PROPERTY_HINT_ENUM" + PROPERTY_HINT_ENUM_SUGGESTION: + return "PROPERTY_HINT_ENUM_SUGGESTION" + PROPERTY_HINT_EXP_EASING: + return "PROPERTY_HINT_EXP_EASING" + PROPERTY_HINT_LINK: + return "PROPERTY_HINT_LINK" + PROPERTY_HINT_FLAGS: + return "PROPERTY_HINT_FLAGS" + PROPERTY_HINT_LAYERS_2D_RENDER: + return "PROPERTY_HINT_LAYERS_2D_RENDER" + PROPERTY_HINT_LAYERS_2D_PHYSICS: + return "PROPERTY_HINT_LAYERS_2D_PHYSICS" + PROPERTY_HINT_LAYERS_2D_NAVIGATION: + return "PROPERTY_HINT_LAYERS_2D_NAVIGATION" + PROPERTY_HINT_LAYERS_3D_RENDER: + return "PROPERTY_HINT_LAYERS_3D_RENDER" + PROPERTY_HINT_LAYERS_3D_PHYSICS: + return "PROPERTY_HINT_LAYERS_3D_PHYSICS" + PROPERTY_HINT_LAYERS_3D_NAVIGATION: + return "PROPERTY_HINT_LAYERS_3D_NAVIGATION" + PROPERTY_HINT_LAYERS_AVOIDANCE: + return "PROPERTY_HINT_LAYERS_AVOIDANCE" + PROPERTY_HINT_FILE: + return "PROPERTY_HINT_FILE" + PROPERTY_HINT_DIR: + return "PROPERTY_HINT_DIR" + PROPERTY_HINT_GLOBAL_FILE: + return "PROPERTY_HINT_GLOBAL_FILE" + PROPERTY_HINT_GLOBAL_DIR: + return "PROPERTY_HINT_GLOBAL_DIR" + PROPERTY_HINT_RESOURCE_TYPE: + return "PROPERTY_HINT_RESOURCE_TYPE" + PROPERTY_HINT_MULTILINE_TEXT: + return "PROPERTY_HINT_MULTILINE_TEXT" + PROPERTY_HINT_EXPRESSION: + return "PROPERTY_HINT_EXPRESSION" + PROPERTY_HINT_PLACEHOLDER_TEXT: + return "PROPERTY_HINT_PLACEHOLDER_TEXT" + PROPERTY_HINT_COLOR_NO_ALPHA: + return "PROPERTY_HINT_COLOR_NO_ALPHA" + PROPERTY_HINT_OBJECT_ID: + return "PROPERTY_HINT_OBJECT_ID" + PROPERTY_HINT_TYPE_STRING: + return "PROPERTY_HINT_TYPE_STRING" + PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE: + return "PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE" + PROPERTY_HINT_OBJECT_TOO_BIG: + return "PROPERTY_HINT_OBJECT_TOO_BIG" + PROPERTY_HINT_NODE_PATH_VALID_TYPES: + return "PROPERTY_HINT_NODE_PATH_VALID_TYPES" + PROPERTY_HINT_SAVE_FILE: + return "PROPERTY_HINT_SAVE_FILE" + PROPERTY_HINT_GLOBAL_SAVE_FILE: + return "PROPERTY_HINT_GLOBAL_SAVE_FILE" + PROPERTY_HINT_INT_IS_OBJECTID: + return "PROPERTY_HINT_INT_IS_OBJECTID" + PROPERTY_HINT_INT_IS_POINTER: + return "PROPERTY_HINT_INT_IS_POINTER" + PROPERTY_HINT_ARRAY_TYPE: + return "PROPERTY_HINT_ARRAY_TYPE" + PROPERTY_HINT_LOCALE_ID: + return "PROPERTY_HINT_LOCALE_ID" + PROPERTY_HINT_LOCALIZABLE_STRING: + return "PROPERTY_HINT_LOCALIZABLE_STRING" + PROPERTY_HINT_NODE_TYPE: + return "PROPERTY_HINT_NODE_TYPE" + PROPERTY_HINT_HIDE_QUATERNION_EDIT: + return "PROPERTY_HINT_HIDE_QUATERNION_EDIT" + PROPERTY_HINT_PASSWORD: + return "PROPERTY_HINT_PASSWORD" + push_error("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.") + return "<invalid hint>" + + +static func get_property_usage_string(usage: int) -> String: + if usage == PROPERTY_USAGE_NONE: + return "PROPERTY_USAGE_NONE" + + const FLAGS: Array[Array] = [ + [PROPERTY_USAGE_DEFAULT, "PROPERTY_USAGE_DEFAULT"], + [PROPERTY_USAGE_STORAGE, "PROPERTY_USAGE_STORAGE"], + [PROPERTY_USAGE_EDITOR, "PROPERTY_USAGE_EDITOR"], + [PROPERTY_USAGE_INTERNAL, "PROPERTY_USAGE_INTERNAL"], + [PROPERTY_USAGE_CHECKABLE, "PROPERTY_USAGE_CHECKABLE"], + [PROPERTY_USAGE_CHECKED, "PROPERTY_USAGE_CHECKED"], + [PROPERTY_USAGE_GROUP, "PROPERTY_USAGE_GROUP"], + [PROPERTY_USAGE_CATEGORY, "PROPERTY_USAGE_CATEGORY"], + [PROPERTY_USAGE_SUBGROUP, "PROPERTY_USAGE_SUBGROUP"], + [PROPERTY_USAGE_CLASS_IS_BITFIELD, "PROPERTY_USAGE_CLASS_IS_BITFIELD"], + [PROPERTY_USAGE_NO_INSTANCE_STATE, "PROPERTY_USAGE_NO_INSTANCE_STATE"], + [PROPERTY_USAGE_RESTART_IF_CHANGED, "PROPERTY_USAGE_RESTART_IF_CHANGED"], + [PROPERTY_USAGE_SCRIPT_VARIABLE, "PROPERTY_USAGE_SCRIPT_VARIABLE"], + [PROPERTY_USAGE_STORE_IF_NULL, "PROPERTY_USAGE_STORE_IF_NULL"], + [PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED, "PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED"], + [PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE, "PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE"], + [PROPERTY_USAGE_CLASS_IS_ENUM, "PROPERTY_USAGE_CLASS_IS_ENUM"], + [PROPERTY_USAGE_NIL_IS_VARIANT, "PROPERTY_USAGE_NIL_IS_VARIANT"], + [PROPERTY_USAGE_ARRAY, "PROPERTY_USAGE_ARRAY"], + [PROPERTY_USAGE_ALWAYS_DUPLICATE, "PROPERTY_USAGE_ALWAYS_DUPLICATE"], + [PROPERTY_USAGE_NEVER_DUPLICATE, "PROPERTY_USAGE_NEVER_DUPLICATE"], + [PROPERTY_USAGE_HIGH_END_GFX, "PROPERTY_USAGE_HIGH_END_GFX"], + [PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT, "PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT"], + [PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT, "PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT"], + [PROPERTY_USAGE_KEYING_INCREMENTS, "PROPERTY_USAGE_KEYING_INCREMENTS"], + [PROPERTY_USAGE_DEFERRED_SET_RESOURCE, "PROPERTY_USAGE_DEFERRED_SET_RESOURCE"], + [PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT, "PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT"], + [PROPERTY_USAGE_EDITOR_BASIC_SETTING, "PROPERTY_USAGE_EDITOR_BASIC_SETTING"], + [PROPERTY_USAGE_READ_ONLY, "PROPERTY_USAGE_READ_ONLY"], + [PROPERTY_USAGE_SECRET, "PROPERTY_USAGE_SECRET"], + ] + + var result: String = "" + + for flag in FLAGS: + if usage & flag[0]: + result += flag[1] + "|" + usage &= ~flag[0] + + if usage != PROPERTY_USAGE_NONE: + push_error("Argument `usage` is invalid. Use `PROPERTY_USAGE_*` constants.") + return "<invalid usage flags>" + + return result.left(-1) diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp index 0446a7aad6..467bedc4b2 100644 --- a/modules/gdscript/tests/test_gdscript.cpp +++ b/modules/gdscript/tests/test_gdscript.cpp @@ -138,12 +138,13 @@ static void recursively_disassemble_functions(const Ref<GDScript> script, const for (const KeyValue<StringName, GDScriptFunction *> &E : script->get_member_functions()) { const GDScriptFunction *func = E.value; - String signature = "Disassembling " + func->get_name().operator String() + "("; - for (int i = 0; i < func->get_argument_count(); i++) { + const MethodInfo &mi = func->get_method_info(); + String signature = "Disassembling " + mi.name + "("; + for (int i = 0; i < mi.arguments.size(); i++) { if (i > 0) { signature += ", "; } - signature += func->get_argument_name(i); + signature += mi.arguments[i].name; } print_line(signature + ")"); #ifdef TOOLS_ENABLED @@ -156,7 +157,7 @@ static void recursively_disassemble_functions(const Ref<GDScript> script, const for (const KeyValue<StringName, Ref<GDScript>> &F : script->get_subclasses()) { const Ref<GDScript> inner_script = F.value; print_line(""); - print_line(vformat("Inner Class: %s", inner_script->get_script_class_name())); + print_line(vformat("Inner Class: %s", inner_script->get_local_name())); print_line(""); recursively_disassemble_functions(inner_script, p_lines); } @@ -222,6 +223,16 @@ void test(TestType p_type) { // Initialize the language for the test routine. init_language(fa->get_path_absolute().get_base_dir()); + // Load global classes. + TypedArray<Dictionary> script_classes = ProjectSettings::get_singleton()->get_global_class_list(); + for (int i = 0; i < script_classes.size(); i++) { + Dictionary c = script_classes[i]; + if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base")) { + continue; + } + ScriptServer::add_global_class(c["class"], c["base"], c["language"], c["path"]); + } + Vector<uint8_t> buf; uint64_t flen = fa->get_length(); buf.resize(flen + 1); diff --git a/modules/gdscript/tests/test_lsp.h b/modules/gdscript/tests/test_lsp.h new file mode 100644 index 0000000000..e57df00e2d --- /dev/null +++ b/modules/gdscript/tests/test_lsp.h @@ -0,0 +1,480 @@ +/**************************************************************************/ +/* test_lsp.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 TEST_LSP_H +#define TEST_LSP_H + +#ifdef TOOLS_ENABLED + +#include "tests/test_macros.h" + +#include "../language_server/gdscript_extend_parser.h" +#include "../language_server/gdscript_language_protocol.h" +#include "../language_server/gdscript_workspace.h" +#include "../language_server/godot_lsp.h" + +#include "core/io/dir_access.h" +#include "core/io/file_access_pack.h" +#include "core/os/os.h" +#include "editor/editor_help.h" +#include "editor/editor_node.h" +#include "modules/gdscript/gdscript_analyzer.h" +#include "modules/regex/regex.h" + +#include "thirdparty/doctest/doctest.h" + +template <> +struct doctest::StringMaker<lsp::Position> { + static doctest::String convert(const lsp::Position &p_val) { + return p_val.to_string().utf8().get_data(); + } +}; + +template <> +struct doctest::StringMaker<lsp::Range> { + static doctest::String convert(const lsp::Range &p_val) { + return p_val.to_string().utf8().get_data(); + } +}; + +template <> +struct doctest::StringMaker<GodotPosition> { + static doctest::String convert(const GodotPosition &p_val) { + return p_val.to_string().utf8().get_data(); + } +}; + +namespace GDScriptTests { + +// LSP GDScript test scripts are located inside project of other GDScript tests: +// Cannot reset `ProjectSettings` (singleton) -> Cannot load another workspace and resources in there. +// -> Reuse GDScript test project. LSP specific scripts are then placed inside `lsp` folder. +// Access via `res://lsp/my_script.notest.gd`. +const String root = "modules/gdscript/tests/scripts/"; + +/* + * After use: + * * `memdelete` returned `GDScriptLanguageProtocol`. + * * Call `GDScriptTests::::finish_language`. + */ +GDScriptLanguageProtocol *initialize(const String &p_root) { + Error err = OK; + Ref<DirAccess> dir(DirAccess::open(p_root, &err)); + REQUIRE_MESSAGE(err == OK, "Could not open specified root directory"); + String absolute_root = dir->get_current_dir(); + init_language(absolute_root); + + GDScriptLanguageProtocol *proto = memnew(GDScriptLanguageProtocol); + + Ref<GDScriptWorkspace> workspace = GDScriptLanguageProtocol::get_singleton()->get_workspace(); + workspace->root = absolute_root; + // On windows: `C:/...` -> `C%3A/...`. + workspace->root_uri = "file:///" + absolute_root.lstrip("/").replace_first(":", "%3A"); + + return proto; +} + +lsp::Position pos(const int p_line, const int p_character) { + lsp::Position p; + p.line = p_line; + p.character = p_character; + return p; +} + +lsp::Range range(const lsp::Position p_start, const lsp::Position p_end) { + lsp::Range r; + r.start = p_start; + r.end = p_end; + return r; +} + +lsp::TextDocumentPositionParams pos_in(const lsp::DocumentUri &p_uri, const lsp::Position p_pos) { + lsp::TextDocumentPositionParams params; + params.textDocument.uri = p_uri; + params.position = p_pos; + return params; +} + +const lsp::DocumentSymbol *test_resolve_symbol_at(const String &p_uri, const lsp::Position p_pos, const String &p_expected_uri, const String &p_expected_name, const lsp::Range &p_expected_range) { + Ref<GDScriptWorkspace> workspace = GDScriptLanguageProtocol::get_singleton()->get_workspace(); + + lsp::TextDocumentPositionParams params = pos_in(p_uri, p_pos); + const lsp::DocumentSymbol *symbol = workspace->resolve_symbol(params); + CHECK(symbol); + + if (symbol) { + CHECK_EQ(symbol->uri, p_expected_uri); + CHECK_EQ(symbol->name, p_expected_name); + CHECK_EQ(symbol->selectionRange, p_expected_range); + } + + return symbol; +} + +struct InlineTestData { + lsp::Range range; + String text; + String name; + String ref; + + static bool try_parse(const Vector<String> &p_lines, const int p_line_number, InlineTestData &r_data) { + String line = p_lines[p_line_number]; + + RegEx regex = RegEx("^\\t*#[ |]*(?<range>(?<left><)?\\^+)(\\s+(?<name>(?!->)\\S+))?(\\s+->\\s+(?<ref>\\S+))?"); + Ref<RegExMatch> match = regex.search(line); + if (match.is_null()) { + return false; + } + + // Find first line without leading comment above current line. + int target_line = p_line_number; + while (target_line >= 0) { + String dedented = p_lines[target_line].lstrip("\t"); + if (!dedented.begins_with("#")) { + break; + } + target_line--; + } + if (target_line < 0) { + return false; + } + r_data.range.start.line = r_data.range.end.line = target_line; + + String marker = match->get_string("range"); + int i = line.find(marker); + REQUIRE(i >= 0); + r_data.range.start.character = i; + if (!match->get_string("left").is_empty()) { + // Include `#` (comment char) in range. + r_data.range.start.character--; + } + r_data.range.end.character = i + marker.length(); + + String target = p_lines[target_line]; + r_data.text = target.substr(r_data.range.start.character, r_data.range.end.character - r_data.range.start.character); + + r_data.name = match->get_string("name"); + r_data.ref = match->get_string("ref"); + + return true; + } +}; + +Vector<InlineTestData> read_tests(const String &p_path) { + Error err; + String source = FileAccess::get_file_as_string(p_path, &err); + REQUIRE_MESSAGE(err == OK, vformat("Cannot read '%s'", p_path)); + + // Format: + // ```gdscript + // var foo = bar + baz + // # | | | | ^^^ name -> ref + // # | | ^^^ -> ref + // # ^^^ name + // + // func my_func(): + // # ^^^^^^^ name + // var value = foo + 42 + // # ^^^^^ name + // print(value) + // # ^^^^^ -> ref + // ``` + // + // * `^`: Range marker. + // * `name`: Unique name. Can contain any characters except whitespace chars. + // * `ref`: Reference to unique name. + // + // Notes: + // * If range should include first content-char (which is occupied by `#`): use `<` for next marker. + // -> Range expands 1 to left (-> includes `#`). + // * Note: Means: Range cannot be single char directly marked by `#`, but must be at least two chars (marked with `#<`). + // * Comment must start at same ident as line its marked (-> because of tab alignment...). + // * Use spaces to align after `#`! -> for correct alignment + // * Between `#` and `^` can be spaces or `|` (to better visualize what's marked below). + PackedStringArray lines = source.split("\n"); + + PackedStringArray names; + Vector<InlineTestData> data; + for (int i = 0; i < lines.size(); i++) { + InlineTestData d; + if (InlineTestData::try_parse(lines, i, d)) { + if (!d.name.is_empty()) { + // Safety check: names must be unique. + if (names.find(d.name) != -1) { + FAIL(vformat("Duplicated name '%s' in '%s'. Names must be unique!", d.name, p_path)); + } + names.append(d.name); + } + + data.append(d); + } + } + + return data; +} + +void test_resolve_symbol(const String &p_uri, const InlineTestData &p_test_data, const Vector<InlineTestData> &p_all_data) { + if (p_test_data.ref.is_empty()) { + return; + } + + SUBCASE(vformat("Can resolve symbol '%s' at %s to '%s'", p_test_data.text, p_test_data.range.to_string(), p_test_data.ref).utf8().get_data()) { + const InlineTestData *target = nullptr; + for (int i = 0; i < p_all_data.size(); i++) { + if (p_all_data[i].name == p_test_data.ref) { + target = &p_all_data[i]; + break; + } + } + REQUIRE_MESSAGE(target, vformat("No target for ref '%s'", p_test_data.ref)); + + Ref<GDScriptWorkspace> workspace = GDScriptLanguageProtocol::get_singleton()->get_workspace(); + lsp::Position pos = p_test_data.range.start; + + SUBCASE("start of identifier") { + pos.character = p_test_data.range.start.character; + test_resolve_symbol_at(p_uri, pos, p_uri, target->text, target->range); + } + + SUBCASE("inside identifier") { + pos.character = (p_test_data.range.end.character + p_test_data.range.start.character) / 2; + test_resolve_symbol_at(p_uri, pos, p_uri, target->text, target->range); + } + + SUBCASE("end of identifier") { + pos.character = p_test_data.range.end.character; + test_resolve_symbol_at(p_uri, pos, p_uri, target->text, target->range); + } + } +} + +Vector<InlineTestData> filter_ref_towards(const Vector<InlineTestData> &p_data, const String &p_name) { + Vector<InlineTestData> res; + + for (const InlineTestData &d : p_data) { + if (d.ref == p_name) { + res.append(d); + } + } + + return res; +} + +void test_resolve_symbols(const String &p_uri, const Vector<InlineTestData> &p_test_data, const Vector<InlineTestData> &p_all_data) { + for (const InlineTestData &d : p_test_data) { + test_resolve_symbol(p_uri, d, p_all_data); + } +} + +void assert_no_errors_in(const String &p_path) { + Error err; + String source = FileAccess::get_file_as_string(p_path, &err); + REQUIRE_MESSAGE(err == OK, vformat("Cannot read '%s'", p_path)); + + GDScriptParser parser; + err = parser.parse(source, p_path, true); + REQUIRE_MESSAGE(err == OK, vformat("Errors while parsing '%s'", p_path)); + + GDScriptAnalyzer analyzer(&parser); + err = analyzer.analyze(); + REQUIRE_MESSAGE(err == OK, vformat("Errors while analyzing '%s'", p_path)); +} + +inline lsp::Position lsp_pos(int line, int character) { + lsp::Position p; + p.line = line; + p.character = character; + return p; +} + +void test_position_roundtrip(lsp::Position p_lsp, GodotPosition p_gd, const PackedStringArray &p_lines) { + GodotPosition actual_gd = GodotPosition::from_lsp(p_lsp, p_lines); + CHECK_EQ(p_gd, actual_gd); + lsp::Position actual_lsp = p_gd.to_lsp(p_lines); + CHECK_EQ(p_lsp, actual_lsp); +} + +// Note: +// * Cursor is BETWEEN chars +// * `va|r` -> cursor between `a`&`r` +// * `var` +// ^ +// -> Character on `r` -> cursor between `a`&`r`s for tests: +// * Line & Char: +// * LSP: both 0-based +// * Godot: both 1-based +TEST_SUITE("[Modules][GDScript][LSP]") { + TEST_CASE("Can convert positions to and from Godot") { + String code = R"(extends Node + +var member := 42 + +func f(): + var value := 42 + return value + member)"; + PackedStringArray lines = code.split("\n"); + + SUBCASE("line after end") { + lsp::Position lsp = lsp_pos(7, 0); + GodotPosition gd(8, 1); + test_position_roundtrip(lsp, gd, lines); + } + SUBCASE("first char in first line") { + lsp::Position lsp = lsp_pos(0, 0); + GodotPosition gd(1, 1); + test_position_roundtrip(lsp, gd, lines); + } + + SUBCASE("with tabs") { + // On `v` in `value` in `var value := ...`. + lsp::Position lsp = lsp_pos(5, 6); + GodotPosition gd(6, 13); + test_position_roundtrip(lsp, gd, lines); + } + + SUBCASE("doesn't fail with column outside of character length") { + lsp::Position lsp = lsp_pos(2, 100); + GodotPosition::from_lsp(lsp, lines); + + GodotPosition gd(3, 100); + gd.to_lsp(lines); + } + + SUBCASE("doesn't fail with line outside of line length") { + lsp::Position lsp = lsp_pos(200, 100); + GodotPosition::from_lsp(lsp, lines); + + GodotPosition gd(300, 100); + gd.to_lsp(lines); + } + + SUBCASE("special case: negative line for root class") { + GodotPosition gd(-1, 0); + lsp::Position expected = lsp_pos(0, 0); + lsp::Position actual = gd.to_lsp(lines); + CHECK_EQ(actual, expected); + } + SUBCASE("special case: lines.length() + 1 for root class") { + GodotPosition gd(lines.size() + 1, 0); + lsp::Position expected = lsp_pos(lines.size(), 0); + lsp::Position actual = gd.to_lsp(lines); + CHECK_EQ(actual, expected); + } + } + TEST_CASE("[workspace][resolve_symbol]") { + GDScriptLanguageProtocol *proto = initialize(root); + REQUIRE(proto); + Ref<GDScriptWorkspace> workspace = GDScriptLanguageProtocol::get_singleton()->get_workspace(); + + { + String path = "res://lsp/local_variables.notest.gd"; + assert_no_errors_in(path); + String uri = workspace->get_file_uri(path); + Vector<InlineTestData> all_test_data = read_tests(path); + SUBCASE("Can get correct ranges for public variables") { + Vector<InlineTestData> test_data = filter_ref_towards(all_test_data, "member"); + test_resolve_symbols(uri, test_data, all_test_data); + } + SUBCASE("Can get correct ranges for local variables") { + Vector<InlineTestData> test_data = filter_ref_towards(all_test_data, "test"); + test_resolve_symbols(uri, test_data, all_test_data); + } + SUBCASE("Can get correct ranges for local parameters") { + Vector<InlineTestData> test_data = filter_ref_towards(all_test_data, "arg"); + test_resolve_symbols(uri, test_data, all_test_data); + } + } + + SUBCASE("Can get correct ranges for indented variables") { + String path = "res://lsp/indentation.notest.gd"; + assert_no_errors_in(path); + String uri = workspace->get_file_uri(path); + Vector<InlineTestData> all_test_data = read_tests(path); + test_resolve_symbols(uri, all_test_data, all_test_data); + } + + SUBCASE("Can get correct ranges for scopes") { + String path = "res://lsp/scopes.notest.gd"; + assert_no_errors_in(path); + String uri = workspace->get_file_uri(path); + Vector<InlineTestData> all_test_data = read_tests(path); + test_resolve_symbols(uri, all_test_data, all_test_data); + } + + SUBCASE("Can get correct ranges for lambda") { + String path = "res://lsp/lambdas.notest.gd"; + assert_no_errors_in(path); + String uri = workspace->get_file_uri(path); + Vector<InlineTestData> all_test_data = read_tests(path); + test_resolve_symbols(uri, all_test_data, all_test_data); + } + + SUBCASE("Can get correct ranges for inner class") { + String path = "res://lsp/class.notest.gd"; + assert_no_errors_in(path); + String uri = workspace->get_file_uri(path); + Vector<InlineTestData> all_test_data = read_tests(path); + test_resolve_symbols(uri, all_test_data, all_test_data); + } + + SUBCASE("Can get correct ranges for inner class") { + String path = "res://lsp/enums.notest.gd"; + assert_no_errors_in(path); + String uri = workspace->get_file_uri(path); + Vector<InlineTestData> all_test_data = read_tests(path); + test_resolve_symbols(uri, all_test_data, all_test_data); + } + + SUBCASE("Can get correct ranges for shadowing & shadowed variables") { + String path = "res://lsp/shadowing_initializer.notest.gd"; + assert_no_errors_in(path); + String uri = workspace->get_file_uri(path); + Vector<InlineTestData> all_test_data = read_tests(path); + test_resolve_symbols(uri, all_test_data, all_test_data); + } + + SUBCASE("Can get correct ranges for properties and getter/setter") { + String path = "res://lsp/properties.notest.gd"; + assert_no_errors_in(path); + String uri = workspace->get_file_uri(path); + Vector<InlineTestData> all_test_data = read_tests(path); + test_resolve_symbols(uri, all_test_data, all_test_data); + } + + memdelete(proto); + finish_language(); + } +} + +} // namespace GDScriptTests + +#endif // TOOLS_ENABLED + +#endif // TEST_LSP_H |