diff options
Diffstat (limited to 'modules/gdscript')
388 files changed, 6186 insertions, 1579 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 9e974b6fdc..33f4198ac1 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -4,23 +4,18 @@ Built-in GDScript functions. </brief_description> <description> - List of core built-in GDScript functions. Math functions and other utilities. Everything else is provided by objects. (Keywords: builtin, built in, global functions.) + A list of GDScript-specific utility functions accessed in any script. + For the list of the global functions and constants see [@GlobalScope]. </description> <tutorials> - <link title="Random number generation">https://docs.godotengine.org/en/latest/tutorials/math/random_number_generation.html</link> </tutorials> <methods> <method name="Color8"> - <return type="Color"> - </return> - <argument index="0" name="r8" type="int"> - </argument> - <argument index="1" name="g8" type="int"> - </argument> - <argument index="2" name="b8" type="int"> - </argument> - <argument index="3" name="a8" type="int" default="255"> - </argument> + <return type="Color" /> + <argument index="0" name="r8" type="int" /> + <argument index="1" name="g8" type="int" /> + <argument index="2" name="b8" type="int" /> + <argument index="3" name="a8" type="int" default="255" /> <description> Returns a color constructed from integer red, green, blue, and alpha channels. Each channel should have 8 bits of information ranging from 0 to 255. [code]r8[/code] red channel @@ -33,12 +28,9 @@ </description> </method> <method name="assert"> - <return type="void"> - </return> - <argument index="0" name="condition" type="bool"> - </argument> - <argument index="1" name="message" type="String" default=""""> - </argument> + <return type="void" /> + <argument index="0" name="condition" type="bool" /> + <argument index="1" name="message" type="String" default="""" /> <description> Asserts that the [code]condition[/code] is [code]true[/code]. If the [code]condition[/code] is [code]false[/code], an error is generated. When running from the editor, the running project will also be paused until you resume it. This can be used as a stronger form of [method @GlobalScope.push_error] for reporting errors to project developers or add-on users. [b]Note:[/b] For performance reasons, the code inside [method assert] is only executed in debug builds or when running the project from the editor. Don't include code that has side effects in an [method assert] call. Otherwise, the project will behave differently when exported in release mode. @@ -54,10 +46,8 @@ </description> </method> <method name="char"> - <return type="String"> - </return> - <argument index="0" name="char" type="int"> - </argument> + <return type="String" /> + <argument index="0" name="char" type="int" /> <description> Returns a character as a String of the given Unicode code point (which is compatible with ASCII code). [codeblock] @@ -68,12 +58,9 @@ </description> </method> <method name="convert"> - <return type="Variant"> - </return> - <argument index="0" name="what" type="Variant"> - </argument> - <argument index="1" name="type" type="int"> - </argument> + <return type="Variant" /> + <argument index="0" name="what" type="Variant" /> + <argument index="1" name="type" type="int" /> <description> Converts from a type to another in the best way possible. The [code]type[/code] parameter uses the [enum Variant.Type] values. [codeblock] @@ -87,17 +74,14 @@ </description> </method> <method name="dict2inst"> - <return type="Object"> - </return> - <argument index="0" name="dictionary" type="Dictionary"> - </argument> + <return type="Object" /> + <argument index="0" name="dictionary" type="Dictionary" /> <description> Converts a dictionary (previously created with [method inst2dict]) back to an instance. Useful for deserializing. </description> </method> <method name="get_stack"> - <return type="Array"> - </return> + <return type="Array" /> <description> Returns an array of dictionaries representing the current call stack. [codeblock] @@ -114,13 +98,12 @@ [codeblock] [{function:bar, line:12, source:res://script.gd}, {function:foo, line:9, source:res://script.gd}, {function:_ready, line:6, source:res://script.gd}] [/codeblock] + [b]Note:[/b] Not supported for calling from threads. Instead, this will return an empty array. </description> </method> <method name="inst2dict"> - <return type="Dictionary"> - </return> - <argument index="0" name="instance" type="Object"> - </argument> + <return type="Dictionary" /> + <argument index="0" name="instance" type="Object" /> <description> Returns the passed instance converted to a dictionary (useful for serializing). [codeblock] @@ -138,10 +121,8 @@ </description> </method> <method name="len"> - <return type="int"> - </return> - <argument index="0" name="var" type="Variant"> - </argument> + <return type="int" /> + <argument index="0" name="var" type="Variant" /> <description> Returns length of Variant [code]var[/code]. Length is the character count of String, element count of Array, size of Dictionary, etc. [b]Note:[/b] Generates a fatal error if Variant can not provide a length. @@ -152,10 +133,8 @@ </description> </method> <method name="load"> - <return type="Resource"> - </return> - <argument index="0" name="path" type="String"> - </argument> + <return type="Resource" /> + <argument index="0" name="path" type="String" /> <description> Loads a resource from the filesystem located at [code]path[/code]. The resource is loaded on the method call (unless it's referenced already elsewhere, e.g. in another script or in the scene), which might cause slight delay, especially when loading scenes. To avoid unnecessary delays when loading something multiple times, either store the resource in a variable or use [method preload]. [b]Note:[/b] Resource paths can be obtained by right-clicking on a resource in the FileSystem dock and choosing "Copy Path" or by dragging the file from the FileSystem dock into the script. @@ -168,42 +147,45 @@ </description> </method> <method name="preload"> - <return type="Resource"> - </return> - <argument index="0" name="path" type="String"> - </argument> + <return type="Resource" /> + <argument index="0" name="path" type="String" /> <description> Returns a [Resource] from the filesystem located at [code]path[/code]. The resource is loaded during script parsing, i.e. is loaded with the script and [method preload] effectively acts as a reference to that resource. Note that the method requires a constant path. If you want to load a resource from a dynamic/variable path, use [method load]. [b]Note:[/b] Resource paths can be obtained by right clicking on a resource in the Assets Panel and choosing "Copy Path" or by dragging the file from the FileSystem dock into the script. [codeblock] # Instance a scene. - var diamond = preload("res://diamond.tscn").instance() + var diamond = preload("res://diamond.tscn").instantiate() [/codeblock] </description> </method> <method name="print_debug" qualifiers="vararg"> - <return type="void"> - </return> + <return type="void" /> <description> - Like [method @GlobalScope.print], but prints only when used in debug mode. + Like [method @GlobalScope.print], but includes the current stack frame when running with the debugger turned on. + Output in the console would look something like this: + [codeblock] + Test print + At: res://test.gd:15:_process() + [/codeblock] + [b]Note:[/b] Not supported for calling from threads. Instead of the stack frame, this will print the thread ID. </description> </method> <method name="print_stack"> - <return type="void"> - </return> + <return type="void" /> <description> - Prints a stack track at code location, only works when running with debugger turned on. + Prints a stack trace at the current code location. Only works when running with debugger turned on. Output in the console would look something like this: [codeblock] Frame 0 - res://test.gd:16 in function '_process' [/codeblock] + [b]Note:[/b] Not supported for calling from threads. Instead of the stack trace, this will print the thread ID. </description> </method> <method name="range" qualifiers="vararg"> - <return type="Array"> - </return> + <return type="Array" /> <description> - Returns an array with the given range. Range can be 1 argument N (0 to N-1), two arguments (initial, final-1) or three arguments (initial, final-1, increment). + Returns an array with the given range. Range can be 1 argument [code]N[/code] (0 to [code]N[/code] - 1), two arguments ([code]initial[/code], [code]final - 1[/code]) or three arguments ([code]initial[/code], [code]final - 1[/code], [code]increment[/code]). Returns an empty array if the range isn't valid (e.g. [code]range(2, 5, -1)[/code] or [code]range(5, 5, 1)[/code]). + Returns an array with the given range. [code]range()[/code] can have 1 argument N ([code]0[/code] to [code]N - 1[/code]), two arguments ([code]initial[/code], [code]final - 1[/code]) or three arguments ([code]initial[/code], [code]final - 1[/code], [code]increment[/code]). [code]increment[/code] can be negative. If [code]increment[/code] is negative, [code]final - 1[/code] will become [code]final + 1[/code]. Also, the initial value must be greater than the final value for the loop to run. [codeblock] print(range(4)) print(range(2, 5)) @@ -215,11 +197,24 @@ [2, 3, 4] [0, 2, 4] [/codeblock] + To iterate over an [Array] backwards, use: + [codeblock] + var array = [3, 6, 9] + var i := array.size() - 1 + while i >= 0: + print(array[i]) + i -= 1 + [/codeblock] + Output: + [codeblock] + 9 + 6 + 3 + [/codeblock] </description> </method> <method name="str" qualifiers="vararg"> - <return type="String"> - </return> + <return type="String" /> <description> Converts one or more arguments to string in the best way possible. [codeblock] @@ -231,26 +226,26 @@ </description> </method> <method name="type_exists"> - <return type="bool"> - </return> - <argument index="0" name="type" type="StringName"> - </argument> + <return type="bool" /> + <argument index="0" name="type" type="StringName" /> <description> </description> </method> </methods> <constants> - <constant name="PI" value="3.141593"> - Constant that represents how many times the diameter of a circle fits around its perimeter. This is equivalent to [code]TAU / 2[/code]. + <constant name="PI" value="3.14159265358979"> + Constant that represents how many times the diameter of a circle fits around its perimeter. This is equivalent to [code]TAU / 2[/code], or 180 degrees in rotations. </constant> - <constant name="TAU" value="6.283185"> - The circle constant, the circumference of the unit circle in radians. + <constant name="TAU" value="6.28318530717959"> + The circle constant, the circumference of the unit circle in radians. This is equivalent to [code]PI * 2[/code], or 360 degrees in rotations. </constant> <constant name="INF" value="inf"> - Positive infinity. For negative infinity, use -INF. + Positive floating-point infinity. This is the result of floating-point division when the divisor is [code]0.0[/code]. For negative infinity, use [code]-INF[/code]. Dividing by [code]-0.0[/code] will result in negative infinity if the numerator is positive, so dividing by [code]0.0[/code] is not the same as dividing by [code]-0.0[/code] (despite [code]0.0 == -0.0[/code] returning [code]true[/code]). + [b]Note:[/b] Numeric infinity is only a concept with floating-point numbers, and has no equivalent for integers. Dividing an integer number by [code]0[/code] will not result in [constant INF] and will result in a run-time error instead. </constant> <constant name="NAN" value="nan"> - "Not a Number", an invalid value. [code]NaN[/code] has special properties, including that it is not equal to itself. It is output by some invalid operations, such as dividing zero by zero. + "Not a Number", an invalid floating-point value. [constant NAN] has special properties, including that it is not equal to itself ([code]NAN == NAN[/code] returns [code]false[/code]). It is output by some invalid operations, such as dividing floating-point [code]0.0[/code] by [code]0.0[/code]. + [b]Note:[/b] "Not a Number" is only a concept with floating-point numbers, and has no equivalent for integers. Dividing an integer [code]0[/code] by [code]0[/code] will not result in [constant NAN] and will result in a run-time error instead. </constant> </constants> </class> diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml index 631a102130..5acb29e748 100644 --- a/modules/gdscript/doc_classes/GDScript.xml +++ b/modules/gdscript/doc_classes/GDScript.xml @@ -8,19 +8,17 @@ [method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes. </description> <tutorials> - <link title="GDScript tutorial index">https://docs.godotengine.org/en/latest/getting_started/scripting/gdscript/index.html</link> + <link title="GDScript documentation index">$DOCS_URL/tutorials/scripting/gdscript/index.html</link> </tutorials> <methods> <method name="get_as_byte_code" qualifiers="const"> - <return type="PackedByteArray"> - </return> + <return type="PackedByteArray" /> <description> Returns byte code for the script source code. </description> </method> <method name="new" qualifiers="vararg"> - <return type="Variant"> - </return> + <return type="Variant" /> <description> Returns a new instance of the script. For example: @@ -32,6 +30,4 @@ </description> </method> </methods> - <constants> - </constants> </class> diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index ca646dff15..6529154e5c 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -45,7 +45,7 @@ static bool _is_bin_symbol(char32_t c) { return (c == '0' || c == '1'); } -Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) { +Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) { Dictionary color_map; Type next_type = NONE; @@ -64,6 +64,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) bool in_function_args = false; bool in_member_variable = false; bool in_node_path = false; + bool in_annotation = false; bool is_hex_notation = false; bool is_bin_notation = false; bool expect_type = false; @@ -357,9 +358,18 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting(int p_line) in_node_path = false; } + if (!in_annotation && in_region == -1 && str[j] == '@') { + in_annotation = true; + } else if (in_region != -1 || is_a_symbol) { + in_annotation = false; + } + if (in_node_path) { next_type = NODE_PATH; color = node_path_color; + } else if (in_annotation) { + next_type = ANNOTATION; + color = annotation_color; } else if (in_keyword) { next_type = KEYWORD; color = keyword_color; @@ -438,36 +448,32 @@ void GDScriptSyntaxHighlighter::_update_cache() { color_regions.clear(); color_region_cache.clear(); - font_color = text_edit->get_theme_color("font_color"); - symbol_color = EDITOR_GET("text_editor/highlighting/symbol_color"); - function_color = EDITOR_GET("text_editor/highlighting/function_color"); - number_color = EDITOR_GET("text_editor/highlighting/number_color"); - member_color = EDITOR_GET("text_editor/highlighting/member_variable_color"); + font_color = text_edit->get_theme_color(SNAME("font_color")); + symbol_color = EDITOR_GET("text_editor/theme/highlighting/symbol_color"); + function_color = EDITOR_GET("text_editor/theme/highlighting/function_color"); + number_color = EDITOR_GET("text_editor/theme/highlighting/number_color"); + member_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color"); /* Engine types. */ - const Color types_color = EDITOR_GET("text_editor/highlighting/engine_type_color"); + const Color types_color = EDITOR_GET("text_editor/theme/highlighting/engine_type_color"); List<StringName> types; ClassDB::get_class_list(&types); - for (List<StringName>::Element *E = types.front(); E; E = E->next()) { - String n = E->get(); - if (n.begins_with("_")) { - n = n.substr(1, n.length()); - } - keywords[n] = types_color; + for (const StringName &E : types) { + keywords[E] = types_color; } /* User types. */ - const Color usertype_color = EDITOR_GET("text_editor/highlighting/user_type_color"); + const Color usertype_color = EDITOR_GET("text_editor/theme/highlighting/user_type_color"); List<StringName> global_classes; ScriptServer::get_global_class_list(&global_classes); - for (List<StringName>::Element *E = global_classes.front(); E; E = E->next()) { - keywords[String(E->get())] = usertype_color; + for (const StringName &E : global_classes) { + keywords[E] = usertype_color; } /* Autoloads. */ - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->value(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.value(); if (info.is_singleton) { keywords[info.name] = usertype_color; } @@ -476,43 +482,41 @@ void GDScriptSyntaxHighlighter::_update_cache() { const GDScriptLanguage *gdscript = GDScriptLanguage::get_singleton(); /* Core types. */ - const Color basetype_color = EDITOR_GET("text_editor/highlighting/base_type_color"); + const Color basetype_color = EDITOR_GET("text_editor/theme/highlighting/base_type_color"); List<String> core_types; gdscript->get_core_type_words(&core_types); - for (List<String>::Element *E = core_types.front(); E; E = E->next()) { - keywords[E->get()] = basetype_color; + for (const String &E : core_types) { + keywords[StringName(E)] = basetype_color; } /* Reserved words. */ - const Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color"); - const Color control_flow_keyword_color = EDITOR_GET("text_editor/highlighting/control_flow_keyword_color"); + const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color"); + const Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color"); List<String> keyword_list; gdscript->get_reserved_words(&keyword_list); - for (List<String>::Element *E = keyword_list.front(); E; E = E->next()) { - if (gdscript->is_control_flow_keyword(E->get())) { - keywords[E->get()] = control_flow_keyword_color; + for (const String &E : keyword_list) { + if (gdscript->is_control_flow_keyword(E)) { + keywords[StringName(E)] = control_flow_keyword_color; } else { - keywords[E->get()] = keyword_color; + keywords[StringName(E)] = keyword_color; } } /* Comments */ - const Color comment_color = EDITOR_GET("text_editor/highlighting/comment_color"); + const Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color"); List<String> comments; gdscript->get_comment_delimiters(&comments); - for (List<String>::Element *E = comments.front(); E; E = E->next()) { - String comment = E->get(); + for (const String &comment : comments) { String beg = comment.get_slice(" ", 0); String end = comment.get_slice_count(" ") > 1 ? comment.get_slice(" ", 1) : String(); add_color_region(beg, end, comment_color, end == ""); } /* Strings */ - const Color string_color = EDITOR_GET("text_editor/highlighting/string_color"); + const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color"); List<String> strings; gdscript->get_string_delimiters(&strings); - for (List<String>::Element *E = strings.front(); E; E = E->next()) { - String string = E->get(); + for (const String &string : strings) { String beg = string.get_slice(" ", 0); String end = string.get_slice_count(" ") > 1 ? string.get_slice(" ", 1) : String(); add_color_region(beg, end, string_color, end == ""); @@ -521,14 +525,14 @@ void GDScriptSyntaxHighlighter::_update_cache() { const Ref<Script> script = _get_edited_resource(); if (script.is_valid()) { /* Member types. */ - const Color member_variable_color = EDITOR_GET("text_editor/highlighting/member_variable_color"); + const Color member_variable_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color"); StringName instance_base = script->get_instance_base_type(); if (instance_base != StringName()) { List<PropertyInfo> plist; ClassDB::get_property_list(instance_base, &plist); - for (List<PropertyInfo>::Element *E = plist.front(); E; E = E->next()) { - String name = E->get().name; - if (E->get().usage & PROPERTY_USAGE_CATEGORY || E->get().usage & PROPERTY_USAGE_GROUP || E->get().usage & PROPERTY_USAGE_SUBGROUP) { + for (const PropertyInfo &E : plist) { + String name = E.name; + if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) { continue; } if (name.find("/") != -1) { @@ -539,39 +543,47 @@ void GDScriptSyntaxHighlighter::_update_cache() { List<String> clist; ClassDB::get_integer_constant_list(instance_base, &clist); - for (List<String>::Element *E = clist.front(); E; E = E->next()) { - member_keywords[E->get()] = member_variable_color; + for (const String &E : clist) { + member_keywords[E] = member_variable_color; } } } const String text_edit_color_theme = EditorSettings::get_singleton()->get("text_editor/theme/color_theme"); - const bool default_theme = text_edit_color_theme == "Default"; + const bool godot_2_theme = text_edit_color_theme == "Godot 2"; - if (default_theme || EditorSettings::get_singleton()->is_dark_theme()) { + if (godot_2_theme || EditorSettings::get_singleton()->is_dark_theme()) { function_definition_color = Color(0.4, 0.9, 1.0); node_path_color = Color(0.39, 0.76, 0.35); + annotation_color = Color(1.0, 0.7, 0.45); } else { function_definition_color = Color(0.0, 0.65, 0.73); node_path_color = Color(0.32, 0.55, 0.29); + annotation_color = Color(0.8, 0.5, 0.25); } - EDITOR_DEF("text_editor/highlighting/gdscript/function_definition_color", function_definition_color); - EDITOR_DEF("text_editor/highlighting/gdscript/node_path_color", node_path_color); - if (text_edit_color_theme == "Adaptive" || default_theme) { + EDITOR_DEF("text_editor/theme/highlighting/gdscript/function_definition_color", function_definition_color); + EDITOR_DEF("text_editor/theme/highlighting/gdscript/node_path_color", node_path_color); + EDITOR_DEF("text_editor/theme/highlighting/gdscript/annotation_color", annotation_color); + if (text_edit_color_theme == "Default" || godot_2_theme) { EditorSettings::get_singleton()->set_initial_value( - "text_editor/highlighting/gdscript/function_definition_color", + "text_editor/theme/highlighting/gdscript/function_definition_color", function_definition_color, true); EditorSettings::get_singleton()->set_initial_value( - "text_editor/highlighting/gdscript/node_path_color", + "text_editor/theme/highlighting/gdscript/node_path_color", node_path_color, true); + EditorSettings::get_singleton()->set_initial_value( + "text_editor/theme/highlighting/gdscript/annotation_color", + annotation_color, + true); } - function_definition_color = EDITOR_GET("text_editor/highlighting/gdscript/function_definition_color"); - node_path_color = EDITOR_GET("text_editor/highlighting/gdscript/node_path_color"); - type_color = EDITOR_GET("text_editor/highlighting/base_type_color"); + function_definition_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/function_definition_color"); + node_path_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/node_path_color"); + annotation_color = EDITOR_GET("text_editor/theme/highlighting/gdscript/annotation_color"); + type_color = EDITOR_GET("text_editor/theme/highlighting/base_type_color"); } void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only) { @@ -604,6 +616,6 @@ void GDScriptSyntaxHighlighter::add_color_region(const String &p_start_key, cons Ref<EditorSyntaxHighlighter> GDScriptSyntaxHighlighter::_create() const { Ref<GDScriptSyntaxHighlighter> syntax_highlighter; - syntax_highlighter.instance(); + syntax_highlighter.instantiate(); return syntax_highlighter; } diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h index 1b57cb1923..07f21b34ae 100644 --- a/modules/gdscript/editor/gdscript_highlighter.h +++ b/modules/gdscript/editor/gdscript_highlighter.h @@ -47,13 +47,14 @@ private: Vector<ColorRegion> color_regions; Map<int, int> color_region_cache; - Dictionary keywords; - Dictionary member_keywords; + HashMap<StringName, Color> keywords; + HashMap<StringName, Color> member_keywords; enum Type { NONE, REGION, NODE_PATH, + ANNOTATION, SYMBOL, NUMBER, FUNCTION, @@ -72,13 +73,14 @@ private: Color number_color; Color member_color; Color node_path_color; + Color annotation_color; Color type_color; void add_color_region(const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only = false); public: virtual void _update_cache() override; - virtual Dictionary _get_line_syntax_highlighting(int p_line) override; + virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) override; virtual String _get_name() const override; virtual Array _get_supported_languages() const override; diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h index fcf438422a..caa80fc24c 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.h +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h @@ -34,7 +34,6 @@ #include "core/templates/set.h" #include "editor/editor_translation_parser.h" #include "modules/gdscript/gdscript_parser.h" -#include "modules/regex/regex.h" class GDScriptEditorTranslationParserPlugin : public EditorTranslationParserPlugin { GDCLASS(GDScriptEditorTranslationParserPlugin, EditorTranslationParserPlugin); diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 859c1acde9..b76c2c0437 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -36,8 +36,8 @@ #include "core/config/project_settings.h" #include "core/core_constants.h" #include "core/core_string_names.h" +#include "core/io/file_access.h" #include "core/io/file_access_encrypted.h" -#include "core/os/file_access.h" #include "core/os/os.h" #include "gdscript_analyzer.h" #include "gdscript_cache.h" @@ -72,19 +72,32 @@ void GDScriptNativeClass::_bind_methods() { } Variant GDScriptNativeClass::_new() { - Object *o = instance(); + Object *o = instantiate(); ERR_FAIL_COND_V_MSG(!o, Variant(), "Class type: '" + String(name) + "' is not instantiable."); - Reference *ref = Object::cast_to<Reference>(o); - if (ref) { - return REF(ref); + RefCounted *rc = Object::cast_to<RefCounted>(o); + if (rc) { + return REF(rc); } else { return o; } } -Object *GDScriptNativeClass::instance() { - return ClassDB::instance(name); +Object *GDScriptNativeClass::instantiate() { + return ClassDB::instantiate(name); +} + +GDScriptFunction *GDScript::_super_constructor(GDScript *p_script) { + if (p_script->initializer) { + return p_script->initializer; + } else { + GDScript *base = p_script->_base; + if (base != nullptr) { + return _super_constructor(base); + } else { + return nullptr; + } + } } void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error) { @@ -98,19 +111,19 @@ void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance p_script->implicit_initializer->call(p_instance, nullptr, 0, r_error); } -GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Callable::CallError &r_error) { +GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error) { /* STEP 1, CREATE */ GDScriptInstance *instance = memnew(GDScriptInstance); - instance->base_ref = p_isref; + instance->base_ref_counted = p_is_ref_counted; instance->members.resize(member_indices.size()); instance->script = Ref<GDScript>(this); instance->owner = p_owner; instance->owner_id = p_owner->get_instance_id(); #ifdef DEBUG_ENABLED //needed for hot reloading - for (Map<StringName, MemberInfo>::Element *E = member_indices.front(); E; E = E->next()) { - instance->member_indices_cache[E->key()] = E->get().index; + for (const KeyValue<StringName, MemberInfo> &E : member_indices) { + instance->member_indices_cache[E.key] = E.value.index; } #endif instance->owner->set_script_instance(instance); @@ -135,6 +148,8 @@ GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argco if (p_argcount < 0) { return instance; } + + initializer = _super_constructor(this); if (initializer != nullptr) { initializer->call(instance, p_args, p_argcount, r_error); if (r_error.error != Callable::CallError::CALL_OK) { @@ -170,13 +185,13 @@ Variant GDScript::_new(const Variant **p_args, int p_argcount, Callable::CallErr ERR_FAIL_COND_V(_baseptr->native.is_null(), Variant()); if (_baseptr->native.ptr()) { - owner = _baseptr->native->instance(); + owner = _baseptr->native->instantiate(); } else { - owner = memnew(Reference); //by default, no base means use reference + owner = memnew(RefCounted); //by default, no base means use reference } ERR_FAIL_COND_V_MSG(!owner, Variant(), "Can't inherit from a virtual class."); - Reference *r = Object::cast_to<Reference>(owner); + RefCounted *r = Object::cast_to<RefCounted>(owner); if (r) { ref = REF(r); } @@ -196,7 +211,7 @@ Variant GDScript::_new(const Variant **p_args, int p_argcount, Callable::CallErr } } -bool GDScript::can_instance() const { +bool GDScript::can_instantiate() const { #ifdef TOOLS_ENABLED return valid && (tool || ScriptServer::is_scripting_enabled()); #else @@ -238,10 +253,10 @@ void GDScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) { void GDScript::_get_script_method_list(List<MethodInfo> *r_list, bool p_include_base) const { const GDScript *current = this; while (current) { - for (const Map<StringName, GDScriptFunction *>::Element *E = current->member_functions.front(); E; E = E->next()) { - GDScriptFunction *func = E->get(); + for (const KeyValue<StringName, GDScriptFunction *> &E : current->member_functions) { + GDScriptFunction *func = E.value; MethodInfo mi; - mi.name = E->key(); + mi.name = E.key; for (int i = 0; i < func->get_argument_count(); i++) { PropertyInfo arginfo = func->get_argument_type(i); #ifdef TOOLS_ENABLED @@ -271,11 +286,11 @@ void GDScript::_get_script_property_list(List<PropertyInfo> *r_list, bool p_incl while (sptr) { Vector<_GDScriptMemberSort> msort; - for (Map<StringName, PropertyInfo>::Element *E = sptr->member_info.front(); E; E = E->next()) { + for (const KeyValue<StringName, PropertyInfo> &E : sptr->member_info) { _GDScriptMemberSort ms; - ERR_CONTINUE(!sptr->member_indices.has(E->key())); - ms.index = sptr->member_indices[E->key()].index; - ms.name = E->key(); + ERR_CONTINUE(!sptr->member_indices.has(E.key)); + ms.index = sptr->member_indices[E.key].index; + ms.name = E.key; msort.push_back(ms); } @@ -291,8 +306,8 @@ void GDScript::_get_script_property_list(List<PropertyInfo> *r_list, bool p_incl sptr = sptr->_base; } - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - r_list->push_back(E->get()); + for (const PropertyInfo &E : props) { + r_list->push_back(E); } } @@ -346,21 +361,21 @@ ScriptInstance *GDScript::instance_create(Object *p_this) { if (top->native.is_valid()) { if (!ClassDB::is_parent_class(p_this->get_class_name(), top->native->get_name())) { if (EngineDebugger::is_active()) { - GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), 1, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); + GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), 1, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'"); } - ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instanced in object of type '" + p_this->get_class() + "'" + "."); + ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(top->native->get_name()) + "', so it can't be instantiated in object of type '" + p_this->get_class() + "'" + "."); } } Callable::CallError unchecked_error; - return _create_instance(nullptr, 0, p_this, Object::cast_to<Reference>(p_this) != nullptr, unchecked_error); + return _create_instance(nullptr, 0, p_this, Object::cast_to<RefCounted>(p_this) != nullptr, unchecked_error); } PlaceHolderScriptInstance *GDScript::placeholder_instance_create(Object *p_this) { #ifdef TOOLS_ENABLED PlaceHolderScriptInstance *si = memnew(PlaceHolderScriptInstance(GDScriptLanguage::get_singleton(), Ref<Script>(this), p_this)); placeholders.insert(si); - _update_exports(); + _update_exports(nullptr, false, si); return si; #else return nullptr; @@ -397,12 +412,12 @@ void GDScript::_update_exports_values(Map<StringName, Variant> &values, List<Pro base_cache->_update_exports_values(values, propnames); } - for (Map<StringName, Variant>::Element *E = member_default_values_cache.front(); E; E = E->next()) { - values[E->key()] = E->get(); + for (const KeyValue<StringName, Variant> &E : member_default_values_cache) { + values[E.key] = E.value; } - for (List<PropertyInfo>::Element *E = members_cache.front(); E; E = E->next()) { - propnames.push_back(E->get()); + for (const PropertyInfo &E : members_cache) { + propnames.push_back(E); } } @@ -412,7 +427,7 @@ void GDScript::_add_doc(const DocData::ClassDoc &p_inner_class) { } else { for (int i = 0; i < docs.size(); i++) { if (docs[i].name == p_inner_class.name) { - docs.remove(i); + docs.remove_at(i); break; } } @@ -456,9 +471,9 @@ void GDScript::_update_doc() { doc.description = doc_description; doc.tutorials = doc_tutorials; - for (Map<String, DocData::EnumDoc>::Element *E = doc_enums.front(); E; E = E->next()) { - if (E->value().description != "") { - doc.enums[E->key()] = E->value().description; + for (const KeyValue<String, DocData::EnumDoc> &E : doc_enums) { + if (E.value.description != "") { + doc.enums[E.key] = E.value.description; } } @@ -481,7 +496,7 @@ void GDScript::_update_doc() { methods[i].return_val.class_name = _get_gdscript_reference_class_name(Object::cast_to<GDScript>(return_type.script_type)); } - // Change class name if argumetn is script reference. + // Change class name if argument is script reference. for (int j = 0; j < fn->get_argument_count(); j++) { GDScriptDataType arg_type = fn->get_argument_type(j); if (arg_type.kind == GDScriptDataType::GDSCRIPT) { @@ -530,36 +545,36 @@ void GDScript::_update_doc() { for (int i = 0; i < signals.size(); i++) { DocData::MethodDoc signal_doc; if (doc_signals.has(signals[i].name)) { - DocData::signal_doc_from_methodinfo(signal_doc, signals[i], signals[i].name); + DocData::signal_doc_from_methodinfo(signal_doc, signals[i], doc_signals[signals[i].name]); } else { DocData::signal_doc_from_methodinfo(signal_doc, signals[i], String()); } doc.signals.push_back(signal_doc); } - for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { - if (subclasses.has(E->key())) { + for (const KeyValue<StringName, Variant> &E : constants) { + if (subclasses.has(E.key)) { continue; } // Enums. bool is_enum = false; - if (E->value().get_type() == Variant::DICTIONARY) { - if (doc_enums.has(E->key())) { + if (E.value.get_type() == Variant::DICTIONARY) { + if (doc_enums.has(E.key)) { is_enum = true; - for (int i = 0; i < doc_enums[E->key()].values.size(); i++) { - doc_enums[E->key()].values.write[i].enumeration = E->key(); - doc.constants.push_back(doc_enums[E->key()].values[i]); + for (int i = 0; i < doc_enums[E.key].values.size(); i++) { + doc_enums[E.key].values.write[i].enumeration = E.key; + doc.constants.push_back(doc_enums[E.key].values[i]); } } } if (!is_enum && doc_enums.has("@unnamed_enums")) { for (int i = 0; i < doc_enums["@unnamed_enums"].values.size(); i++) { - if (E->key() == doc_enums["@unnamed_enums"].values[i].name) { + if (E.key == doc_enums["@unnamed_enums"].values[i].name) { is_enum = true; DocData::ConstantDoc constant_doc; constant_doc.enumeration = "@unnamed_enums"; - DocData::constant_doc_from_variant(constant_doc, E->key(), E->value(), doc_enums["@unnamed_enums"].values[i].description); + DocData::constant_doc_from_variant(constant_doc, E.key, E.value, doc_enums["@unnamed_enums"].values[i].description); doc.constants.push_back(constant_doc); break; } @@ -568,23 +583,23 @@ void GDScript::_update_doc() { if (!is_enum) { DocData::ConstantDoc constant_doc; String doc_description; - if (doc_constants.has(E->key())) { - doc_description = doc_constants[E->key()]; + if (doc_constants.has(E.key)) { + doc_description = doc_constants[E.key]; } - DocData::constant_doc_from_variant(constant_doc, E->key(), E->value(), doc_description); + DocData::constant_doc_from_variant(constant_doc, E.key, E.value, doc_description); doc.constants.push_back(constant_doc); } } - for (Map<StringName, Ref<GDScript>>::Element *E = subclasses.front(); E; E = E->next()) { - E->get()->_update_doc(); + for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { + E.value->_update_doc(); } _add_doc(doc); } #endif -bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) { +bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderScriptInstance *p_instance_to_update) { #ifdef TOOLS_ENABLED static Vector<GDScript *> base_caches; @@ -625,9 +640,9 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) { String path = ""; if (String(c->extends_path) != "" && String(c->extends_path) != get_path()) { path = c->extends_path; - if (path.is_rel_path()) { + if (path.is_relative_path()) { String base = get_path(); - if (base == "" || base.is_rel_path()) { + if (base == "" || base.is_relative_path()) { ERR_PRINT(("Could not resolve relative path for parent class: " + path).utf8().get_data()); } else { path = base.get_base_dir().plus_file(path); @@ -721,15 +736,19 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call) { } } - if (placeholders.size()) { //hm :( + if ((changed || p_instance_to_update) && placeholders.size()) { //hm :( // update placeholders if any Map<StringName, Variant> values; List<PropertyInfo> propnames; _update_exports_values(values, propnames); - for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { - E->get()->update(propnames, values); + if (changed) { + for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { + E->get()->update(propnames, values); + } + } else { + p_instance_to_update->update(propnames, values); } } @@ -765,8 +784,16 @@ void GDScript::update_exports() { void GDScript::_set_subclass_path(Ref<GDScript> &p_sc, const String &p_path) { p_sc->path = p_path; - for (Map<StringName, Ref<GDScript>>::Element *E = p_sc->subclasses.front(); E; E = E->next()) { - _set_subclass_path(E->get(), p_path); + for (KeyValue<StringName, Ref<GDScript>> &E : p_sc->subclasses) { + _set_subclass_path(E.value, p_path); + } +} + +String GDScript::_get_debug_path() const { + if (is_built_in() && !get_name().is_empty()) { + return get_name() + " (" + get_path().get_slice("::", 0) + ")"; + } else { + return get_path(); } } @@ -813,10 +840,10 @@ Error GDScript::reload(bool p_keep_state) { Error err = parser.parse(source, path, false); if (err) { if (EngineDebugger::is_active()) { - GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message); + GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message); } // TODO: Show all error messages. - _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT); + _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), false, ERR_HANDLER_SCRIPT); ERR_FAIL_V(ERR_PARSE_ERROR); } @@ -825,12 +852,12 @@ Error GDScript::reload(bool p_keep_state) { if (err) { if (EngineDebugger::is_active()) { - GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message); + GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), parser.get_errors().front()->get().line, "Parser Error: " + parser.get_errors().front()->get().message); } const List<GDScriptParser::ParserError>::Element *e = parser.get_errors().front(); while (e != nullptr) { - _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), e->get().line, ("Parse Error: " + e->get().message).utf8().get_data(), ERR_HANDLER_SCRIPT); + _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), e->get().line, ("Parse Error: " + e->get().message).utf8().get_data(), false, ERR_HANDLER_SCRIPT); e = e->next(); } ERR_FAIL_V(ERR_PARSE_ERROR); @@ -848,28 +875,27 @@ Error GDScript::reload(bool p_keep_state) { if (err) { if (can_run) { if (EngineDebugger::is_active()) { - GDScriptLanguage::get_singleton()->debug_break_parse(get_path(), compiler.get_error_line(), "Parser Error: " + compiler.get_error()); + GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), compiler.get_error_line(), "Parser Error: " + compiler.get_error()); } - _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), compiler.get_error_line(), ("Compile Error: " + compiler.get_error()).utf8().get_data(), ERR_HANDLER_SCRIPT); + _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), compiler.get_error_line(), ("Compile Error: " + compiler.get_error()).utf8().get_data(), false, ERR_HANDLER_SCRIPT); ERR_FAIL_V(ERR_COMPILATION_FAILED); } else { return err; } } #ifdef DEBUG_ENABLED - for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E; E = E->next()) { - const GDScriptWarning &warning = E->get(); + for (const GDScriptWarning &warning : parser.get_warnings()) { if (EngineDebugger::is_active()) { Vector<ScriptLanguage::StackInfo> si; - EngineDebugger::get_script_debugger()->send_error("", get_path(), warning.start_line, warning.get_name(), warning.get_message(), ERR_HANDLER_WARNING, si); + EngineDebugger::get_script_debugger()->send_error("", get_path(), warning.start_line, warning.get_name(), warning.get_message(), false, ERR_HANDLER_WARNING, si); } } #endif valid = true; - for (Map<StringName, Ref<GDScript>>::Element *E = subclasses.front(); E; E = E->next()) { - _set_subclass_path(E->get(), path); + for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { + _set_subclass_path(E.value, path); } _init_rpc_methods_properties(); @@ -883,8 +909,8 @@ ScriptLanguage *GDScript::get_language() const { void GDScript::get_constants(Map<StringName, Variant> *p_constants) { if (p_constants) { - for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { - (*p_constants)[E->key()] = E->value(); + for (const KeyValue<StringName, Variant> &E : constants) { + (*p_constants)[E.key] = E.value; } } } @@ -897,68 +923,10 @@ void GDScript::get_members(Set<StringName> *p_members) { } } -Vector<ScriptNetData> GDScript::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> GDScript::get_rpc_methods() const { return rpc_functions; } -uint16_t GDScript::get_rpc_method_id(const StringName &p_method) const { - for (int i = 0; i < rpc_functions.size(); i++) { - if (rpc_functions[i].name == p_method) { - return i; - } - } - return UINT16_MAX; -} - -StringName GDScript::get_rpc_method(const uint16_t p_rpc_method_id) const { - if (p_rpc_method_id >= rpc_functions.size()) { - return StringName(); - } - return rpc_functions[p_rpc_method_id].name; -} - -MultiplayerAPI::RPCMode GDScript::get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const { - if (p_rpc_method_id >= rpc_functions.size()) { - return MultiplayerAPI::RPC_MODE_DISABLED; - } - return rpc_functions[p_rpc_method_id].mode; -} - -MultiplayerAPI::RPCMode GDScript::get_rpc_mode(const StringName &p_method) const { - return get_rpc_mode_by_id(get_rpc_method_id(p_method)); -} - -Vector<ScriptNetData> GDScript::get_rset_properties() const { - return rpc_variables; -} - -uint16_t GDScript::get_rset_property_id(const StringName &p_variable) const { - for (int i = 0; i < rpc_variables.size(); i++) { - if (rpc_variables[i].name == p_variable) { - return i; - } - } - return UINT16_MAX; -} - -StringName GDScript::get_rset_property(const uint16_t p_rset_member_id) const { - if (p_rset_member_id >= rpc_variables.size()) { - return StringName(); - } - return rpc_variables[p_rset_member_id].name; -} - -MultiplayerAPI::RPCMode GDScript::get_rset_mode_by_id(const uint16_t p_rset_member_id) const { - if (p_rset_member_id >= rpc_variables.size()) { - return MultiplayerAPI::RPC_MODE_DISABLED; - } - return rpc_variables[p_rset_member_id].mode; -} - -MultiplayerAPI::RPCMode GDScript::get_rset_mode(const StringName &p_variable) const { - return get_rset_mode_by_id(get_rset_property_id(p_variable)); -} - Variant GDScript::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { GDScript *top = this; while (top) { @@ -1019,7 +987,7 @@ 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_NOEDITOR | PROPERTY_USAGE_INTERNAL)); + p_properties->push_back(PropertyInfo(Variant::STRING, "script/source", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); } void GDScript::_bind_methods() { @@ -1045,10 +1013,10 @@ Error GDScript::load_source_code(const String &p_path) { ERR_FAIL_COND_V(err, err); } - int len = f->get_len(); + uint64_t len = f->get_length(); sourcef.resize(len + 1); uint8_t *w = sourcef.ptrw(); - int r = f->get_buffer(w, len); + uint64_t r = f->get_buffer(w, len); f->close(); memdelete(f); ERR_FAIL_COND_V(r != len, ERR_CANT_OPEN); @@ -1072,9 +1040,9 @@ const Map<StringName, GDScriptFunction *> &GDScript::debug_get_member_functions( } StringName GDScript::debug_get_member_by_index(int p_idx) const { - for (const Map<StringName, MemberInfo>::Element *E = member_indices.front(); E; E = E->next()) { - if (E->get().index == p_idx) { - return E->key(); + for (const KeyValue<StringName, MemberInfo> &E : member_indices) { + if (E.value.index == p_idx) { + return E.key; } } @@ -1119,12 +1087,12 @@ 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 Map<StringName, Vector<StringName>>::Element *E = _signals.front(); E; E = E->next()) { + for (const KeyValue<StringName, Vector<StringName>> &E : _signals) { MethodInfo mi; - mi.name = E->key(); - for (int i = 0; i < E->get().size(); i++) { + mi.name = E.key; + for (int i = 0; i < E.value.size(); i++) { PropertyInfo arg; - arg.name = E->get()[i]; + arg.name = E.value[i]; mi.arguments.push_back(arg); } r_list->push_back(mi); @@ -1182,11 +1150,11 @@ void GDScript::_save_orphaned_subclasses() { }; Vector<ClassRefWithName> weak_subclasses; // collect subclasses ObjectID and name - for (Map<StringName, Ref<GDScript>>::Element *E = subclasses.front(); E; E = E->next()) { - E->get()->_owner = nullptr; //bye, you are no longer owned cause I died + for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { + E.value->_owner = nullptr; //bye, you are no longer owned cause I died ClassRefWithName subclass; - subclass.id = E->get()->get_instance_id(); - subclass.fully_qualified_name = E->get()->fully_qualified_name; + subclass.id = E.value->get_instance_id(); + subclass.fully_qualified_name = E.value->fully_qualified_name; weak_subclasses.push_back(subclass); } @@ -1210,34 +1178,20 @@ void GDScript::_save_orphaned_subclasses() { void GDScript::_init_rpc_methods_properties() { // Copy the base rpc methods so we don't mask their IDs. rpc_functions.clear(); - rpc_variables.clear(); if (base.is_valid()) { rpc_functions = base->rpc_functions; - rpc_variables = base->rpc_variables; } GDScript *cscript = this; Map<StringName, Ref<GDScript>>::Element *sub_E = subclasses.front(); while (cscript) { // RPC Methods - for (Map<StringName, GDScriptFunction *>::Element *E = cscript->member_functions.front(); E; E = E->next()) { - if (E->get()->get_rpc_mode() != MultiplayerAPI::RPC_MODE_DISABLED) { - ScriptNetData nd; - nd.name = E->key(); - nd.mode = E->get()->get_rpc_mode(); - if (-1 == rpc_functions.find(nd)) { - rpc_functions.push_back(nd); - } - } - } - // RSet - for (Map<StringName, MemberInfo>::Element *E = cscript->member_indices.front(); E; E = E->next()) { - if (E->get().rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) { - ScriptNetData nd; - nd.name = E->key(); - nd.mode = E->get().rpc_mode; - if (-1 == rpc_variables.find(nd)) { - rpc_variables.push_back(nd); + for (KeyValue<StringName, GDScriptFunction *> &E : cscript->member_functions) { + Multiplayer::RPCConfig config = E.value->get_rpc_config(); + if (config.rpc_mode != Multiplayer::RPC_MODE_DISABLED) { + config.name = E.value->get_name(); + if (rpc_functions.find(config) == -1) { + rpc_functions.push_back(config); } } } @@ -1254,8 +1208,7 @@ void GDScript::_init_rpc_methods_properties() { } // Sort so we are 100% that they are always the same. - rpc_functions.sort_custom<SortNetData>(); - rpc_variables.sort_custom<SortNetData>(); + rpc_functions.sort_custom<Multiplayer::SortRPCConfig>(); } GDScript::~GDScript() { @@ -1270,8 +1223,8 @@ GDScript::~GDScript() { } } - for (Map<StringName, GDScriptFunction *>::Element *E = member_functions.front(); E; E = E->next()) { - memdelete(E->get()); + for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { + memdelete(E.value); } if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown. @@ -1312,6 +1265,8 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { call(member->setter, &val, 1, err); if (err.error == Callable::CallError::CALL_OK) { return true; //function exists, call was successful + } else { + return false; } } else { if (member->data_type.has_type) { @@ -1497,11 +1452,11 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const //instance a fake script for editing the values Vector<_GDScriptMemberSort> msort; - for (Map<StringName, PropertyInfo>::Element *F = sptr->member_info.front(); F; F = F->next()) { + for (const KeyValue<StringName, PropertyInfo> &F : sptr->member_info) { _GDScriptMemberSort ms; - ERR_CONTINUE(!sptr->member_indices.has(F->key())); - ms.index = sptr->member_indices[F->key()].index; - ms.name = F->key(); + ERR_CONTINUE(!sptr->member_indices.has(F.key)); + ms.index = sptr->member_indices[F.key].index; + ms.name = F.key; msort.push_back(ms); } @@ -1514,19 +1469,19 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const sptr = sptr->_base; } - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - p_properties->push_back(E->get()); + for (const PropertyInfo &E : props) { + p_properties->push_back(E); } } void GDScriptInstance::get_method_list(List<MethodInfo> *p_list) const { const GDScript *sptr = script.ptr(); while (sptr) { - for (Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.front(); E; E = E->next()) { + for (const KeyValue<StringName, GDScriptFunction *> &E : sptr->member_functions) { MethodInfo mi; - mi.name = E->key(); + mi.name = E.key; mi.flags |= METHOD_FLAG_FROM_SCRIPT; - for (int i = 0; i < E->get()->get_argument_count(); i++) { + 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); @@ -1611,46 +1566,10 @@ ScriptLanguage *GDScriptInstance::get_language() { return GDScriptLanguage::get_singleton(); } -Vector<ScriptNetData> GDScriptInstance::get_rpc_methods() const { +const Vector<Multiplayer::RPCConfig> GDScriptInstance::get_rpc_methods() const { return script->get_rpc_methods(); } -uint16_t GDScriptInstance::get_rpc_method_id(const StringName &p_method) const { - return script->get_rpc_method_id(p_method); -} - -StringName GDScriptInstance::get_rpc_method(const uint16_t p_rpc_method_id) const { - return script->get_rpc_method(p_rpc_method_id); -} - -MultiplayerAPI::RPCMode GDScriptInstance::get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const { - return script->get_rpc_mode_by_id(p_rpc_method_id); -} - -MultiplayerAPI::RPCMode GDScriptInstance::get_rpc_mode(const StringName &p_method) const { - return script->get_rpc_mode(p_method); -} - -Vector<ScriptNetData> GDScriptInstance::get_rset_properties() const { - return script->get_rset_properties(); -} - -uint16_t GDScriptInstance::get_rset_property_id(const StringName &p_variable) const { - return script->get_rset_property_id(p_variable); -} - -StringName GDScriptInstance::get_rset_property(const uint16_t p_rset_member_id) const { - return script->get_rset_property(p_rset_member_id); -} - -MultiplayerAPI::RPCMode GDScriptInstance::get_rset_mode_by_id(const uint16_t p_rset_member_id) const { - return script->get_rset_mode_by_id(p_rset_member_id); -} - -MultiplayerAPI::RPCMode GDScriptInstance::get_rset_mode(const StringName &p_variable) const { - return script->get_rset_mode(p_variable); -} - void GDScriptInstance::reload_members() { #ifdef DEBUG_ENABLED @@ -1660,10 +1579,10 @@ void GDScriptInstance::reload_members() { new_members.resize(script->member_indices.size()); //pass the values to the new indices - for (Map<StringName, GDScript::MemberInfo>::Element *E = script->member_indices.front(); E; E = E->next()) { - if (member_indices_cache.has(E->key())) { - Variant value = members[member_indices_cache[E->key()]]; - new_members.write[E->get().index] = value; + for (KeyValue<StringName, GDScript::MemberInfo> &E : script->member_indices) { + if (member_indices_cache.has(E.key)) { + Variant value = members[member_indices_cache[E.key]]; + new_members.write[E.value.index] = value; } } @@ -1672,8 +1591,8 @@ void GDScriptInstance::reload_members() { //pass the values to the new indices member_indices_cache.clear(); - for (Map<StringName, GDScript::MemberInfo>::Element *E = script->member_indices.front(); E; E = E->next()) { - member_indices_cache[E->key()] = E->get().index; + for (const KeyValue<StringName, GDScript::MemberInfo> &E : script->member_indices) { + member_indices_cache[E.key] = E.value.index; } #endif @@ -1681,7 +1600,7 @@ void GDScriptInstance::reload_members() { GDScriptInstance::GDScriptInstance() { owner = nullptr; - base_ref = false; + base_ref_counted = false; } GDScriptInstance::~GDScriptInstance() { @@ -1742,24 +1661,18 @@ void GDScriptLanguage::init() { _add_global(StaticCString::create("PI"), Math_PI); _add_global(StaticCString::create("TAU"), Math_TAU); - _add_global(StaticCString::create("INF"), Math_INF); - _add_global(StaticCString::create("NAN"), Math_NAN); + _add_global(StaticCString::create("INF"), INFINITY); + _add_global(StaticCString::create("NAN"), NAN); //populate native classes List<StringName> class_list; ClassDB::get_class_list(&class_list); - for (List<StringName>::Element *E = class_list.front(); E; E = E->next()) { - StringName n = E->get(); - String s = String(n); - if (s.begins_with("_")) { - n = s.substr(1, s.length()); - } - + for (const StringName &n : class_list) { if (globals.has(n)) { continue; } - Ref<GDScriptNativeClass> nc = memnew(GDScriptNativeClass(E->get())); + Ref<GDScriptNativeClass> nc = memnew(GDScriptNativeClass(n)); _add_global(n, nc); } @@ -1767,8 +1680,8 @@ void GDScriptLanguage::init() { List<Engine::Singleton> singletons; Engine::get_singleton()->get_singletons(&singletons); - for (List<Engine::Singleton>::Element *E = singletons.front(); E; E = E->next()) { - _add_global(E->get().name, E->get().ptr); + for (const Engine::Singleton &E : singletons) { + _add_global(E.name, E.ptr); } #ifdef TESTS_ENABLED @@ -1911,10 +1824,10 @@ void GDScriptLanguage::reload_all_scripts() { scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order - for (List<Ref<GDScript>>::Element *E = scripts.front(); E; E = E->next()) { - print_verbose("GDScript: Reloading: " + E->get()->get_path()); - E->get()->load_source_code(E->get()->get_path()); - E->get()->reload(true); + for (Ref<GDScript> &script : scripts) { + print_verbose("GDScript: Reloading: " + script->get_path()); + script->load_source_code(script->get_path()); + script->reload(true); } #endif } @@ -1943,21 +1856,21 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order - for (List<Ref<GDScript>>::Element *E = scripts.front(); E; E = E->next()) { - bool reload = E->get() == p_script || to_reload.has(E->get()->get_base()); + for (Ref<GDScript> &script : scripts) { + bool reload = script == p_script || to_reload.has(script->get_base()); if (!reload) { continue; } - to_reload.insert(E->get(), Map<ObjectID, List<Pair<StringName, Variant>>>()); + to_reload.insert(script, Map<ObjectID, List<Pair<StringName, Variant>>>()); if (!p_soft_reload) { //save state and remove script from instances - Map<ObjectID, List<Pair<StringName, Variant>>> &map = to_reload[E->get()]; + Map<ObjectID, List<Pair<StringName, Variant>>> &map = to_reload[script]; - while (E->get()->instances.front()) { - Object *obj = E->get()->instances.front()->get(); + while (script->instances.front()) { + Object *obj = script->instances.front()->get(); //save instance info List<Pair<StringName, Variant>> state; if (obj->get_script_instance()) { @@ -1970,8 +1883,8 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so //same thing for placeholders #ifdef TOOLS_ENABLED - while (E->get()->placeholders.size()) { - Object *obj = E->get()->placeholders.front()->get()->get_owner(); + while (script->placeholders.size()) { + Object *obj = script->placeholders.front()->get()->get_owner(); //save instance info if (obj->get_script_instance()) { @@ -1981,27 +1894,27 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so obj->set_script(Variant()); } else { // no instance found. Let's remove it so we don't loop forever - E->get()->placeholders.erase(E->get()->placeholders.front()->get()); + script->placeholders.erase(script->placeholders.front()->get()); } } #endif - for (Map<ObjectID, List<Pair<StringName, Variant>>>::Element *F = E->get()->pending_reload_state.front(); F; F = F->next()) { - map[F->key()] = F->get(); //pending to reload, use this one instead + for (const KeyValue<ObjectID, List<Pair<StringName, Variant>>> &F : script->pending_reload_state) { + map[F.key] = F.value; //pending to reload, use this one instead } } } - for (Map<Ref<GDScript>, Map<ObjectID, List<Pair<StringName, Variant>>>>::Element *E = to_reload.front(); E; E = E->next()) { - Ref<GDScript> scr = E->key(); + for (KeyValue<Ref<GDScript>, Map<ObjectID, List<Pair<StringName, Variant>>>> &E : to_reload) { + Ref<GDScript> scr = E.key; scr->reload(p_soft_reload); //restore state if saved - for (Map<ObjectID, List<Pair<StringName, Variant>>>::Element *F = E->get().front(); F; F = F->next()) { - List<Pair<StringName, Variant>> &saved_state = F->get(); + for (KeyValue<ObjectID, List<Pair<StringName, Variant>>> &F : E.value) { + List<Pair<StringName, Variant>> &saved_state = F.value; - Object *obj = ObjectDB::get_instance(F->key()); + Object *obj = ObjectDB::get_instance(F.key); if (!obj) { continue; } @@ -2130,22 +2043,22 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const { List<StringName> functions; GDScriptUtilityFunctions::get_function_list(&functions); - for (const List<StringName>::Element *E = functions.front(); E; E = E->next()) { - p_words->push_back(String(E->get())); + for (const StringName &E : functions) { + p_words->push_back(String(E)); } } bool GDScriptLanguage::is_control_flow_keyword(String p_keyword) const { return p_keyword == "break" || - p_keyword == "continue" || - p_keyword == "elif" || - p_keyword == "else" || - p_keyword == "if" || - p_keyword == "for" || - p_keyword == "match" || - p_keyword == "pass" || - p_keyword == "return" || - p_keyword == "while"; + p_keyword == "continue" || + p_keyword == "elif" || + p_keyword == "else" || + p_keyword == "if" || + p_keyword == "for" || + p_keyword == "match" || + p_keyword == "pass" || + p_keyword == "return" || + p_keyword == "while"; } bool GDScriptLanguage::handles_global_class_type(const String &p_type) const { @@ -2169,9 +2082,9 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b if (err == OK) { const GDScriptParser::ClassNode *c = parser.get_tree(); if (r_icon_path) { - if (c->icon_path.is_empty() || c->icon_path.is_abs_path()) { + if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) { *r_icon_path = c->icon_path; - } else if (c->icon_path.is_rel_path()) { + } else if (c->icon_path.is_relative_path()) { *r_icon_path = p_path.get_base_dir().plus_file(c->icon_path).simplify_path(); } } @@ -2199,7 +2112,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b break; } String subpath = subclass->extends_path; - if (subpath.is_rel_path()) { + if (subpath.is_relative_path()) { subpath = path.get_base_dir().plus_file(subpath).simplify_path(); } @@ -2218,7 +2131,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b const GDScriptParser::ClassNode *inner_class = subclass->members[i].m_class; if (inner_class->identifier->name == extend_classes[0]) { - extend_classes.remove(0); + extend_classes.remove_at(0); found = true; subclass = inner_class; break; @@ -2237,7 +2150,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b break; } } else { - *r_base_type = "Reference"; + *r_base_type = "RefCounted"; subclass = nullptr; } } @@ -2307,15 +2220,15 @@ GDScriptLanguage::~GDScriptLanguage() { // is not the same as before). script->reference(); - for (Map<StringName, GDScriptFunction *>::Element *E = script->member_functions.front(); E; E = E->next()) { - GDScriptFunction *func = E->get(); + for (KeyValue<StringName, GDScriptFunction *> &E : script->member_functions) { + GDScriptFunction *func = E.value; for (int i = 0; i < func->argument_types.size(); i++) { func->argument_types.write[i].script_type_ref = Ref<Script>(); } func->return_type.script_type_ref = Ref<Script>(); } - for (Map<StringName, GDScript::MemberInfo>::Element *E = script->member_indices.front(); E; E = E->next()) { - E->get().data_type.script_type_ref = Ref<Script>(); + for (KeyValue<StringName, GDScript::MemberInfo> &E : script->member_indices) { + E.value.data_type.script_type_ref = Ref<Script>(); } s = s->next(); @@ -2357,7 +2270,7 @@ RES ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_ori if (script.is_null()) { // Don't fail loading because of parsing error. - script.instance(); + script.instantiate(); } if (r_error) { @@ -2401,8 +2314,8 @@ void ResourceFormatLoaderGDScript::get_dependencies(const String &p_path, List<S return; } - for (const List<String>::Element *E = parser.get_dependencies().front(); E; E = E->next()) { - p_dependencies->push_back(E->get()); + for (const String &E : parser.get_dependencies()) { + p_dependencies->push_back(E); } } diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 6df66e876d..ade4f247c9 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -39,8 +39,8 @@ #include "core/object/script_language.h" #include "gdscript_function.h" -class GDScriptNativeClass : public Reference { - GDCLASS(GDScriptNativeClass, Reference); +class GDScriptNativeClass : public RefCounted { + GDCLASS(GDScriptNativeClass, RefCounted); StringName name; @@ -51,7 +51,7 @@ protected: public: _FORCE_INLINE_ const StringName &get_name() const { return name; } Variant _new(); - Object *instance(); + Object *instantiate(); GDScriptNativeClass(const StringName &p_name); }; @@ -64,7 +64,6 @@ class GDScript : public Script { int index = 0; StringName setter; StringName getter; - MultiplayerAPI::RPCMode rpc_mode; GDScriptDataType data_type; }; @@ -80,14 +79,13 @@ class GDScript : public Script { GDScript *_base = nullptr; //fast pointer access GDScript *_owner = nullptr; //for subclasses - Set<StringName> members; //members are just indices to the instanced script. + Set<StringName> members; //members are just indices to the instantiated script. Map<StringName, Variant> constants; Map<StringName, GDScriptFunction *> member_functions; - Map<StringName, MemberInfo> member_indices; //members are just indices to the instanced script. + Map<StringName, MemberInfo> member_indices; //members are just indices to the instantiated script. Map<StringName, Ref<GDScript>> subclasses; Map<StringName, Vector<StringName>> _signals; - Vector<ScriptNetData> rpc_functions; - Vector<ScriptNetData> rpc_variables; + Vector<Multiplayer::RPCConfig> rpc_functions; #ifdef TOOLS_ENABLED @@ -132,10 +130,12 @@ class GDScript : public Script { SelfList<GDScriptFunctionState>::List pending_func_states; + GDScriptFunction *_super_constructor(GDScript *p_script); void _super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error); - GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Callable::CallError &r_error); + GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error); void _set_subclass_path(Ref<GDScript> &p_sc, const String &p_path); + String _get_debug_path() const; #ifdef TOOLS_ENABLED Set<PlaceHolderScriptInstance *> placeholders; @@ -149,7 +149,7 @@ class GDScript : public Script { #endif - bool _update_exports(bool *r_err = nullptr, bool p_recursive_call = false); + bool _update_exports(bool *r_err = nullptr, bool p_recursive_call = false, PlaceHolderScriptInstance *p_instance_to_update = nullptr); void _save_orphaned_subclasses(); void _init_rpc_methods_properties(); @@ -158,7 +158,7 @@ class GDScript : public Script { void _get_script_method_list(List<MethodInfo> *r_list, bool p_include_base) const; void _get_script_signal_list(List<MethodInfo> *r_list, bool p_include_base) const; - // This method will map the class name from "Reference" to "MyClass.InnerClass". + // This method will map the class name from "RefCounted" to "MyClass.InnerClass". static String _get_gdscript_reference_class_name(const GDScript *p_gdscript); protected: @@ -197,7 +197,7 @@ public: StringName debug_get_member_by_index(int p_idx) const; Variant _new(const Variant **p_args, int p_argcount, Callable::CallError &r_error); - virtual bool can_instance() const override; + virtual bool can_instantiate() const override; virtual Ref<Script> get_base_script() const override; @@ -247,17 +247,7 @@ public: virtual void get_constants(Map<StringName, Variant> *p_constants) override; virtual void get_members(Set<StringName> *p_members) override; - virtual Vector<ScriptNetData> get_rpc_methods() const override; - virtual uint16_t get_rpc_method_id(const StringName &p_method) const override; - virtual StringName get_rpc_method(const uint16_t p_rpc_method_id) const override; - virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const override; - virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const override; - - virtual Vector<ScriptNetData> get_rset_properties() const override; - virtual uint16_t get_rset_property_id(const StringName &p_variable) const override; - virtual StringName get_rset_property(const uint16_t p_variable_id) const override; - virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(const uint16_t p_variable_id) const override; - virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const override; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const override; #ifdef TOOLS_ENABLED virtual bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; } @@ -281,7 +271,7 @@ class GDScriptInstance : public ScriptInstance { Map<StringName, int> member_indices_cache; //used only for hot script reloading #endif Vector<Variant> members; - bool base_ref; + bool base_ref_counted; SelfList<GDScriptFunctionState>::List pending_func_states; @@ -310,17 +300,7 @@ public: void reload_members(); - virtual Vector<ScriptNetData> get_rpc_methods() const; - virtual uint16_t get_rpc_method_id(const StringName &p_method) const; - virtual StringName get_rpc_method(const uint16_t p_rpc_method_id) const; - virtual MultiplayerAPI::RPCMode get_rpc_mode_by_id(const uint16_t p_rpc_method_id) const; - virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const; - - virtual Vector<ScriptNetData> get_rset_properties() const; - virtual uint16_t get_rset_property_id(const StringName &p_variable) const; - virtual StringName get_rset_property(const uint16_t p_variable_id) const; - virtual MultiplayerAPI::RPCMode get_rset_mode_by_id(const uint16_t p_variable_id) const; - virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const; + virtual const Vector<Multiplayer::RPCConfig> get_rpc_methods() const; GDScriptInstance(); ~GDScriptInstance(); @@ -468,7 +448,7 @@ public: virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const; virtual bool is_using_templates(); virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script); - virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptLanguage::Warning> *r_warnings = nullptr, Set<int> *r_safe_lines = nullptr) const; + virtual bool validate(const String &p_script, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptLanguage::ScriptError> *r_errors = nullptr, List<ScriptLanguage::Warning> *r_warnings = nullptr, Set<int> *r_safe_lines = nullptr) const; virtual Script *create_script() const; virtual bool has_named_classes() const; virtual bool supports_builtin_mode() const; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 17ae52f3ab..1ecde53dd0 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -30,11 +30,12 @@ #include "gdscript_analyzer.h" +#include "core/config/engine.h" #include "core/config/project_settings.h" +#include "core/io/file_access.h" #include "core/io/resource_loader.h" #include "core/object/class_db.h" #include "core/object/script_language.h" -#include "core/os/file_access.h" #include "core/templates/hash_map.h" #include "gdscript.h" #include "gdscript_utility_functions.h" @@ -112,11 +113,10 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_native type.is_meta_type = true; List<StringName> enum_values; - StringName real_native_name = GDScriptParser::get_real_class_name(p_native_class); - ClassDB::get_enum_constants(real_native_name, p_enum_name, &enum_values); + ClassDB::get_enum_constants(p_native_class, p_enum_name, &enum_values); - for (const List<StringName>::Element *E = enum_values.front(); E != nullptr; E = E->next()) { - type.enum_values[E->get()] = ClassDB::get_integer_constant(real_native_name, E->get()); + for (const StringName &E : enum_values) { + type.enum_values[E] = ClassDB::get_integer_constant(p_native_class, E); } return type; @@ -132,6 +132,81 @@ static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) { return type; } +bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class) { + if (p_class->members_indices.has(p_member_name)) { + int index = p_class->members_indices[p_member_name]; + const GDScriptParser::ClassNode::Member *member = &p_class->members[index]; + + if (member->type == GDScriptParser::ClassNode::Member::VARIABLE || + member->type == GDScriptParser::ClassNode::Member::CONSTANT || + member->type == GDScriptParser::ClassNode::Member::ENUM || + member->type == GDScriptParser::ClassNode::Member::ENUM_VALUE || + member->type == GDScriptParser::ClassNode::Member::CLASS || + member->type == GDScriptParser::ClassNode::Member::SIGNAL) { + return true; + } + } + + return false; +} + +bool GDScriptAnalyzer::has_member_name_conflict_in_native_type(const StringName &p_member_name, const StringName &p_native_type_string) { + if (ClassDB::has_signal(p_native_type_string, p_member_name)) { + return true; + } + if (ClassDB::has_property(p_native_type_string, p_member_name)) { + return true; + } + if (ClassDB::has_integer_constant(p_native_type_string, p_member_name)) { + return true; + } + + return false; +} + +Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string) { + if (has_member_name_conflict_in_native_type(p_member_name, p_native_type_string)) { + push_error(vformat(R"(Member "%s" redefined (original in native class '%s'))", p_member_name, p_native_type_string), p_member_node); + return ERR_PARSE_ERROR; + } + + if (class_exists(p_member_name)) { + push_error(vformat(R"(The member "%s" shadows a native class.)", p_member_name), p_member_node); + return ERR_PARSE_ERROR; + } + + 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; + } + + return OK; +} + +Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node) { + const GDScriptParser::DataType *current_data_type = &p_class_node->base_type; + while (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::CLASS) { + GDScriptParser::ClassNode *current_class_node = current_data_type->class_type; + if (has_member_name_conflict_in_script_class(p_member_name, current_class_node)) { + push_error(vformat(R"(The member "%s" already exists in a parent class.)", p_member_name), + p_member_node); + return ERR_PARSE_ERROR; + } + current_data_type = ¤t_class_node->base_type; + } + + if (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::NATIVE) { + if (current_data_type->native_type != StringName("")) { + return check_native_member_name_conflict( + p_member_name, + p_member_node, + current_data_type->native_type); + } + } + + return OK; +} + Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) { if (p_class->base_type.is_set()) { // Already resolved @@ -148,6 +223,17 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, p_class->fqcn = p_class->outer->fqcn + "::" + String(p_class->identifier->name); } + if (p_class->identifier) { + StringName class_name = p_class->identifier->name; + if (class_exists(class_name)) { + push_error(vformat(R"(Class "%s" hides a native class.)", class_name), p_class->identifier); + } else if (ScriptServer::is_global_class(class_name) && (ScriptServer::get_global_class_path(class_name) != parser->script_path || p_class != parser->head)) { + push_error(vformat(R"(Class "%s" hides a global script class.)", class_name), p_class->identifier); + } else if (ProjectSettings::get_singleton()->has_autoload(class_name) && ProjectSettings::get_singleton()->get_autoload(class_name).is_singleton) { + push_error(vformat(R"(Class "%s" hides an autoload singleton.)", class_name), p_class->identifier); + } + } + GDScriptParser::DataType result; // Set datatype for class. @@ -158,12 +244,13 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, class_type.kind = GDScriptParser::DataType::CLASS; class_type.class_type = p_class; class_type.script_path = parser->script_path; + class_type.builtin_type = Variant::OBJECT; p_class->set_datatype(class_type); if (!p_class->extends_used) { result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; result.kind = GDScriptParser::DataType::NATIVE; - result.native_type = "Reference"; + result.native_type = "RefCounted"; } else { result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; @@ -172,6 +259,9 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, int extends_index = 0; if (!p_class->extends_path.is_empty()) { + if (p_class->extends_path.is_relative_path()) { + p_class->extends_path = class_type.script_path.get_base_dir().plus_file(p_class->extends_path).simplify_path(); + } Ref<GDScriptParserRef> parser = get_parser_for(p_class->extends_path); if (parser.is_null()) { push_error(vformat(R"(Could not resolve super class path "%s".)", p_class->extends_path), p_class); @@ -229,7 +319,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); return err; } - } else if (class_exists(name) && ClassDB::can_instance(GDScriptParser::get_real_class_name(name))) { + } else if (class_exists(name) && ClassDB::can_instantiate(name)) { base.kind = GDScriptParser::DataType::NATIVE; base.native_type = name; } else { @@ -380,6 +470,8 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type GDScriptParser::DataType container_type = resolve_datatype(p_type->container_type); if (container_type.kind != GDScriptParser::DataType::VARIANT) { + container_type.is_meta_type = false; + container_type.is_constant = false; result.set_container_element_type(container_type); } } @@ -406,7 +498,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return GDScriptParser::DataType(); } result = ref->get_parser()->head->get_datatype(); - } else if (ClassDB::has_enum(GDScriptParser::get_real_class_name(parser->current_class->base_type.native_type), first)) { + } else if (ClassDB::has_enum(parser->current_class->base_type.native_type, first)) { // Native enum in current class. result = make_native_enum_type(parser->current_class->base_type.native_type, first); } else { @@ -433,8 +525,28 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type case GDScriptParser::ClassNode::Member::CONSTANT: if (member.constant->get_datatype().is_meta_type) { result = member.constant->get_datatype(); + result.is_meta_type = false; found = true; break; + } else if (Ref<Script>(member.constant->initializer->reduced_value).is_valid()) { + Ref<GDScript> gdscript = member.constant->initializer->reduced_value; + if (gdscript.is_valid()) { + Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_path()); + if (ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) { + push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_path()), p_type); + return GDScriptParser::DataType(); + } + result = ref->get_parser()->head->get_datatype(); + result.is_meta_type = false; + } else { + Ref<GDScript> script = member.constant->initializer->reduced_value; + result.kind = GDScriptParser::DataType::SCRIPT; + result.builtin_type = Variant::OBJECT; + result.script_type = script; + result.script_path = script->get_path(); + result.native_type = script->get_instance_base_type(); + } + break; } [[fallthrough]]; default: @@ -469,7 +581,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type } } else if (result.kind == GDScriptParser::DataType::NATIVE) { // Only enums allowed for native. - if (ClassDB::has_enum(GDScriptParser::get_real_class_name(result.native_type), p_type->type_chain[1]->name)) { + if (ClassDB::has_enum(result.native_type, p_type->type_chain[1]->name)) { if (p_type->type_chain.size() > 2) { push_error(R"(Enums cannot contain nested types.)", p_type->type_chain[2]); } else { @@ -505,6 +617,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas switch (member.type) { case GDScriptParser::ClassNode::Member::VARIABLE: { + check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable); + GDScriptParser::DataType datatype; datatype.kind = GDScriptParser::DataType::VARIANT; datatype.type_source = GDScriptParser::DataType::UNDETECTED; @@ -543,6 +657,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } else { // TODO: Add warning. mark_node_unsafe(member.variable->initializer); + member.variable->use_conversion_assign = true; } } else if (datatype.builtin_type == Variant::INT && member.variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { #ifdef DEBUG_ENABLED @@ -552,6 +667,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas if (member.variable->initializer->get_datatype().is_variant()) { // TODO: Warn unsafe assign. mark_node_unsafe(member.variable->initializer); + member.variable->use_conversion_assign = true; } } } else if (member.variable->infer_datatype) { @@ -571,11 +687,13 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas member.variable->set_datatype(datatype); // Apply annotations. - for (List<GDScriptParser::AnnotationNode *>::Element *E = member.variable->annotations.front(); E; E = E->next()) { - E->get()->apply(parser, member.variable); + for (GDScriptParser::AnnotationNode *&E : member.variable->annotations) { + E->apply(parser, member.variable); } } break; case GDScriptParser::ClassNode::Member::CONSTANT: { + check_class_member_name_conflict(p_class, member.constant->identifier->name, member.constant); + reduce_expression(member.constant->initializer); GDScriptParser::DataType specified_type; @@ -585,8 +703,9 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas specified_type.is_meta_type = false; } - GDScriptParser::DataType datatype = member.constant->get_datatype(); + GDScriptParser::DataType datatype; if (member.constant->initializer) { + datatype = member.constant->initializer->get_datatype(); if (member.constant->initializer->type == GDScriptParser::Node::ARRAY) { GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(member.constant->initializer); const_fold_array(array); @@ -620,11 +739,13 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas member.constant->set_datatype(datatype); // Apply annotations. - for (List<GDScriptParser::AnnotationNode *>::Element *E = member.constant->annotations.front(); E; E = E->next()) { - E->get()->apply(parser, member.constant); + for (GDScriptParser::AnnotationNode *&E : member.constant->annotations) { + E->apply(parser, member.constant); } } break; case GDScriptParser::ClassNode::Member::SIGNAL: { + check_class_member_name_conflict(p_class, member.signal->identifier->name, member.signal); + for (int j = 0; j < member.signal->parameters.size(); j++) { GDScriptParser::DataType signal_type = resolve_datatype(member.signal->parameters[j]->datatype_specifier); signal_type.is_meta_type = false; @@ -639,11 +760,13 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas member.signal->set_datatype(signal_type); // Apply annotations. - for (List<GDScriptParser::AnnotationNode *>::Element *E = member.signal->annotations.front(); E; E = E->next()) { - E->get()->apply(parser, member.signal); + for (GDScriptParser::AnnotationNode *&E : member.signal->annotations) { + E->apply(parser, member.signal); } } break; case GDScriptParser::ClassNode::Member::ENUM: { + check_class_member_name_conflict(p_class, member.m_enum->identifier->name, member.m_enum); + GDScriptParser::DataType enum_type; enum_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; enum_type.kind = GDScriptParser::DataType::ENUM; @@ -686,8 +809,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas member.m_enum->set_datatype(enum_type); // Apply annotations. - for (List<GDScriptParser::AnnotationNode *>::Element *E = member.m_enum->annotations.front(); E; E = E->next()) { - E->get()->apply(parser, member.m_enum); + for (GDScriptParser::AnnotationNode *&E : member.m_enum->annotations) { + E->apply(parser, member.m_enum); } } break; case GDScriptParser::ClassNode::Member::FUNCTION: @@ -695,6 +818,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas break; case GDScriptParser::ClassNode::Member::ENUM_VALUE: { if (member.enum_value.custom_value) { + check_class_member_name_conflict(p_class, member.enum_value.identifier->name, member.enum_value.custom_value); + current_enum = member.enum_value.parent_enum; reduce_expression(member.enum_value.custom_value); current_enum = nullptr; @@ -708,6 +833,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas member.enum_value.resolved = true; } } else { + check_class_member_name_conflict(p_class, member.enum_value.identifier->name, member.enum_value.parent_enum); + if (member.enum_value.index > 0) { member.enum_value.value = member.enum_value.parent_enum->values[member.enum_value.index - 1].value + 1; } else { @@ -720,7 +847,8 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas p_class->members.write[i].enum_value = member.enum_value; } break; case GDScriptParser::ClassNode::Member::CLASS: - break; // Done later. + check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class); + break; case GDScriptParser::ClassNode::Member::UNDEFINED: ERR_PRINT("Trying to resolve undefined member."); break; @@ -749,18 +877,34 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { GDScriptParser::ClassNode *previous_class = parser->current_class; parser->current_class = p_class; - // Do functions now. + // Do functions and properties now. for (int i = 0; i < p_class->members.size(); i++) { GDScriptParser::ClassNode::Member member = p_class->members[i]; - if (member.type != GDScriptParser::ClassNode::Member::FUNCTION) { - continue; - } + if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) { + resolve_function_body(member.function); + + // Apply annotations. + for (GDScriptParser::AnnotationNode *&E : member.function->annotations) { + E->apply(parser, 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); - resolve_function_body(member.function); + 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()); + } - // Apply annotations. - for (List<GDScriptParser::AnnotationNode *>::Element *E = member.function->annotations.front(); E; E = E->next()) { - E->get()->apply(parser, member.function); + resolve_function_body(member.variable->setter); + } + } } } @@ -776,17 +920,80 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { resolve_class_body(member.m_class); } - // Check unused variables. + // Check unused variables and datatypes of property getters and setters. for (int i = 0; i < p_class->members.size(); i++) { GDScriptParser::ClassNode::Member member = p_class->members[i]; - if (member.type != GDScriptParser::ClassNode::Member::VARIABLE) { - continue; - } + if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { #ifdef DEBUG_ENABLED - if (member.variable->usages == 0 && String(member.variable->identifier->name).begins_with("_")) { - parser->push_warning(member.variable->identifier, GDScriptWarning::UNUSED_PRIVATE_CLASS_VARIABLE, member.variable->identifier->name); - } + if (member.variable->usages == 0 && String(member.variable->identifier->name).begins_with("_")) { + parser->push_warning(member.variable->identifier, GDScriptWarning::UNUSED_PRIVATE_CLASS_VARIABLE, member.variable->identifier->name); + } +#endif + + if (member.variable->property == GDScriptParser::VariableNode::PROP_SETGET) { + GDScriptParser::FunctionNode *getter_function = nullptr; + GDScriptParser::FunctionNode *setter_function = nullptr; + + bool has_valid_getter = false; + bool has_valid_setter = false; + + if (member.variable->getter_pointer != nullptr) { + if (p_class->has_function(member.variable->getter_pointer->name)) { + getter_function = p_class->get_member(member.variable->getter_pointer->name).function; + } + + if (getter_function == nullptr) { + push_error(vformat(R"(Getter "%s" not found.)", member.variable->getter_pointer->name), member.variable); + + } else if (getter_function->parameters.size() != 0 || getter_function->datatype.has_no_type()) { + push_error(vformat(R"(Function "%s" cannot be used as getter because of its signature.)", getter_function->identifier->name), member.variable); + + } else if (!is_type_compatible(member.variable->datatype, getter_function->datatype, true)) { + push_error(vformat(R"(Function with return type "%s" cannot be used as getter for a property of type "%s".)", getter_function->datatype.to_string(), member.variable->datatype.to_string()), member.variable); + + } else { + has_valid_getter = true; + +#ifdef DEBUG_ENABLED + if (member.variable->datatype.builtin_type == Variant::INT && getter_function->datatype.builtin_type == Variant::FLOAT) { + parser->push_warning(member.variable, GDScriptWarning::NARROWING_CONVERSION); + } #endif + } + } + + if (member.variable->setter_pointer != nullptr) { + if (p_class->has_function(member.variable->setter_pointer->name)) { + setter_function = p_class->get_member(member.variable->setter_pointer->name).function; + } + + if (setter_function == nullptr) { + push_error(vformat(R"(Setter "%s" not found.)", member.variable->setter_pointer->name), member.variable); + + } else if (setter_function->parameters.size() != 1) { + push_error(vformat(R"(Function "%s" cannot be used as setter because of its signature.)", setter_function->identifier->name), member.variable); + + } else if (!is_type_compatible(member.variable->datatype, setter_function->parameters[0]->datatype, true)) { + push_error(vformat(R"(Function with argument type "%s" cannot be used as setter for a property of type "%s".)", setter_function->parameters[0]->datatype.to_string(), member.variable->datatype.to_string()), member.variable); + + } else { + has_valid_setter = true; + +#ifdef DEBUG_ENABLED + if (member.variable->datatype.builtin_type == Variant::FLOAT && setter_function->parameters[0]->datatype.builtin_type == Variant::INT) { + parser->push_warning(member.variable, GDScriptWarning::NARROWING_CONVERSION); + } +#endif + } + } + + if (member.variable->datatype.is_variant() && has_valid_getter && has_valid_setter) { + if (!is_type_compatible(getter_function->datatype, setter_function->parameters[0]->datatype, true)) { + push_error(vformat(R"(Getter with type "%s" cannot be used along with setter of type "%s".)", getter_function->datatype.to_string(), setter_function->parameters[0]->datatype.to_string()), member.variable); + } + } + } + } } } @@ -863,7 +1070,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node) { case GDScriptParser::Node::SUBSCRIPT: case GDScriptParser::Node::TERNARY_OPERATOR: case GDScriptParser::Node::UNARY_OPERATOR: - reduce_expression(static_cast<GDScriptParser::ExpressionNode *>(p_node)); + reduce_expression(static_cast<GDScriptParser::ExpressionNode *>(p_node), true); break; case GDScriptParser::Node::BREAK: case GDScriptParser::Node::BREAKPOINT: @@ -910,7 +1117,10 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * return_type.is_meta_type = false; p_function->set_datatype(return_type); if (p_function->return_type) { - push_error("Constructor cannot have an explicit return type.", p_function->return_type); + GDScriptParser::DataType declared_return = resolve_datatype(p_function->return_type); + if (declared_return.kind != GDScriptParser::DataType::BUILTIN || declared_return.builtin_type != Variant::NIL) { + push_error("Constructor cannot have an explicit return type.", p_function->return_type); + } } } else { GDScriptParser::DataType return_type = resolve_datatype(p_function->return_type); @@ -1067,12 +1277,30 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { } } - if (!list_resolved) { + GDScriptParser::DataType variable_type; + if (list_resolved) { + variable_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + variable_type.kind = GDScriptParser::DataType::BUILTIN; + variable_type.builtin_type = Variant::INT; // Can this ever be a float or something else? + p_for->variable->set_datatype(variable_type); + } else if (p_for->list) { resolve_node(p_for->list); + if (p_for->list->datatype.has_container_element_type()) { + variable_type = p_for->list->datatype.get_container_element_type(); + variable_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + } else if (p_for->list->datatype.is_typed_container_type()) { + variable_type = p_for->list->datatype.get_typed_container_type(); + variable_type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + } else { + // Last resort + // TODO: Must other cases be handled? Must we mark as unsafe? + variable_type.type_source = GDScriptParser::DataType::UNDETECTED; + variable_type.kind = GDScriptParser::DataType::VARIANT; + } + } + if (p_for->variable) { + p_for->variable->set_datatype(variable_type); } - - // TODO: If list is a typed array, the variable should be an element. - // Also applicable for constant range() (so variable is int or float). resolve_suite(p_for->loop); p_for->set_datatype(p_for->loop->get_datatype()); @@ -1145,6 +1373,7 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable } else { // TODO: Add warning. mark_node_unsafe(p_variable->initializer); + p_variable->use_conversion_assign = true; } #ifdef DEBUG_ENABLED } else if (type.builtin_type == Variant::INT && p_variable->initializer->get_datatype().builtin_type == Variant::FLOAT) { @@ -1154,6 +1383,7 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable if (p_variable->initializer->get_datatype().is_variant()) { // TODO: Warn unsafe assign. mark_node_unsafe(p_variable->initializer); + p_variable->use_conversion_assign = true; } } } else if (p_variable->infer_datatype) { @@ -1358,8 +1588,7 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame } if (p_parameter->datatype_specifier != nullptr) { - resolve_datatype(p_parameter->datatype_specifier); - result = p_parameter->datatype_specifier->get_datatype(); + result = resolve_datatype(p_parameter->datatype_specifier); result.is_meta_type = false; if (p_parameter->default_value != nullptr) { @@ -1371,18 +1600,30 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame } } + if (result.builtin_type == Variant::Type::NIL && result.type_source == GDScriptParser::DataType::ANNOTATED_INFERRED && p_parameter->datatype_specifier == nullptr) { + push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is "null".)", p_parameter->identifier->name), p_parameter->default_value); + } + p_parameter->set_datatype(result); } void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) { GDScriptParser::DataType result; + GDScriptParser::DataType expected_type; + bool has_expected_type = false; + + if (parser->current_function != nullptr) { + expected_type = parser->current_function->get_datatype(); + has_expected_type = true; + } + if (p_return->return_value != nullptr) { reduce_expression(p_return->return_value); if (p_return->return_value->type == GDScriptParser::Node::ARRAY) { // Check if assigned value is an array literal, so we can make it a typed array too if appropriate. - if (parser->current_function->get_datatype().has_container_element_type() && p_return->return_value->type == GDScriptParser::Node::ARRAY) { - update_array_literal_element_type(parser->current_function->get_datatype(), static_cast<GDScriptParser::ArrayNode *>(p_return->return_value)); + if (has_expected_type && expected_type.has_container_element_type() && p_return->return_value->type == GDScriptParser::Node::ARRAY) { + update_array_literal_element_type(expected_type, static_cast<GDScriptParser::ArrayNode *>(p_return->return_value)); } } result = p_return->return_value->get_datatype(); @@ -1394,30 +1635,31 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) { result.is_constant = true; } - GDScriptParser::DataType function_type = parser->current_function->get_datatype(); - function_type.is_meta_type = false; - if (function_type.is_hard_type()) { - if (!is_type_compatible(function_type, result)) { - // Try other way. Okay but not safe. - if (!is_type_compatible(result, function_type)) { - push_error(vformat(R"(Cannot return value of type "%s" because the function return type is "%s".)", result.to_string(), function_type.to_string()), p_return); - } else { - // TODO: Add warning. - mark_node_unsafe(p_return); - } + if (has_expected_type) { + expected_type.is_meta_type = false; + if (expected_type.is_hard_type()) { + if (!is_type_compatible(expected_type, result)) { + // Try other way. Okay but not safe. + if (!is_type_compatible(result, expected_type)) { + push_error(vformat(R"(Cannot return value of type "%s" because the function return type is "%s".)", result.to_string(), expected_type.to_string()), p_return); + } else { + // TODO: Add warning. + mark_node_unsafe(p_return); + } #ifdef DEBUG_ENABLED - } else if (function_type.builtin_type == Variant::INT && result.builtin_type == Variant::FLOAT) { - parser->push_warning(p_return, GDScriptWarning::NARROWING_CONVERSION); - } else if (result.is_variant()) { - mark_node_unsafe(p_return); + } else if (expected_type.builtin_type == Variant::INT && result.builtin_type == Variant::FLOAT) { + parser->push_warning(p_return, GDScriptWarning::NARROWING_CONVERSION); + } else if (result.is_variant()) { + mark_node_unsafe(p_return); #endif + } } } p_return->set_datatype(result); } -void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expression) { +void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expression, bool p_is_root) { // This one makes some magic happen. if (p_expression == nullptr) { @@ -1445,7 +1687,7 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre reduce_binary_op(static_cast<GDScriptParser::BinaryOpNode *>(p_expression)); break; case GDScriptParser::Node::CALL: - reduce_call(static_cast<GDScriptParser::CallNode *>(p_expression)); + reduce_call(static_cast<GDScriptParser::CallNode *>(p_expression), p_is_root); break; case GDScriptParser::Node::CAST: reduce_cast(static_cast<GDScriptParser::CastNode *>(p_expression)); @@ -1561,6 +1803,7 @@ void GDScriptAnalyzer::update_array_literal_element_type(const GDScriptParser::D } } if (all_same_type) { + element_type.is_constant = false; array_type.set_container_element_type(element_type); } else if (all_have_type) { push_error(vformat(R"(Variant array is not compatible with an array of type "%s".)", p_base_type.get_container_element_type().to_string()), p_array_literal); @@ -1591,7 +1834,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig push_error("Cannot assign a new value to a constant.", p_assignment->assignee); } - if (!assignee_type.is_variant() && !assigned_value_type.is_variant()) { + if (!assignee_type.is_variant() && assigned_value_type.is_hard_type()) { bool compatible = true; GDScriptParser::DataType op_type = assigned_value_type; if (p_assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { @@ -1608,6 +1851,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig } else { // TODO: Add warning. mark_node_unsafe(p_assignment); + p_assignment->use_conversion_assign = true; } } else { // TODO: Warning in this case. @@ -1621,6 +1865,9 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig if (assignee_type.has_no_type() || assigned_value_type.is_variant()) { mark_node_unsafe(p_assignment); + if (assignee_type.is_hard_type()) { + p_assignment->use_conversion_assign = true; + } } if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) { @@ -1636,21 +1883,27 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig identifier->variable_source->set_datatype(id_type); } } break; + case GDScriptParser::IdentifierNode::FUNCTION_PARAMETER: { + GDScriptParser::DataType id_type = identifier->parameter_source->get_datatype(); + if (!id_type.is_hard_type()) { + id_type.kind = GDScriptParser::DataType::VARIANT; + id_type.type_source = GDScriptParser::DataType::UNDETECTED; + identifier->parameter_source->set_datatype(id_type); + } + } break; case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: { GDScriptParser::DataType id_type = identifier->variable_source->get_datatype(); if (!id_type.is_hard_type()) { - id_type = assigned_value_type; - id_type.type_source = GDScriptParser::DataType::INFERRED; - id_type.is_constant = false; + id_type.kind = GDScriptParser::DataType::VARIANT; + id_type.type_source = GDScriptParser::DataType::UNDETECTED; identifier->variable_source->set_datatype(id_type); } } break; case GDScriptParser::IdentifierNode::LOCAL_ITERATOR: { GDScriptParser::DataType id_type = identifier->bind_source->get_datatype(); if (!id_type.is_hard_type()) { - id_type = assigned_value_type; - id_type.type_source = GDScriptParser::DataType::INFERRED; - id_type.is_constant = false; + id_type.kind = GDScriptParser::DataType::VARIANT; + id_type.type_source = GDScriptParser::DataType::UNDETECTED; identifier->variable_source->set_datatype(id_type); } } break; @@ -1676,16 +1929,25 @@ void GDScriptAnalyzer::reduce_await(GDScriptParser::AwaitNode *p_await) { p_await->set_datatype(await_type); return; } + + GDScriptParser::DataType awaiting_type; + if (p_await->to_await->type == GDScriptParser::Node::CALL) { reduce_call(static_cast<GDScriptParser::CallNode *>(p_await->to_await), true); + awaiting_type = p_await->to_await->get_datatype(); } else { reduce_expression(p_await->to_await); } - p_await->is_constant = p_await->to_await->is_constant; - p_await->reduced_value = p_await->to_await->reduced_value; + if (p_await->to_await->is_constant) { + p_await->is_constant = p_await->to_await->is_constant; + p_await->reduced_value = p_await->to_await->reduced_value; - GDScriptParser::DataType awaiting_type = p_await->to_await->get_datatype(); + awaiting_type = p_await->to_await->get_datatype(); + } else { + awaiting_type.kind = GDScriptParser::DataType::VARIANT; + awaiting_type.type_source = GDScriptParser::DataType::UNDETECTED; + } p_await->set_datatype(awaiting_type); @@ -1770,10 +2032,10 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o } else { if (p_binary_op->variant_op < Variant::OP_MAX) { bool valid = false; - result = get_operation_type(p_binary_op->variant_op, p_binary_op->left_operand->get_datatype(), right_type, valid, p_binary_op); + result = get_operation_type(p_binary_op->variant_op, left_type, right_type, valid, p_binary_op); if (!valid) { - push_error(vformat(R"(Invalid operands "%s" and "%s" for "%s" operator.)", p_binary_op->left_operand->get_datatype().to_string(), right_type.to_string(), Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op); + push_error(vformat(R"(Invalid operands "%s" and "%s" for "%s" operator.)", left_type.to_string(), right_type.to_string(), Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op); } } else { if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) { @@ -1805,7 +2067,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o p_binary_op->set_datatype(result); } -void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_await) { +void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) { bool all_is_constant = true; Map<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing. for (int i = 0; i < p_call->arguments.size(); i++) { @@ -1918,9 +2180,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa Variant::get_constructor_list(builtin_type, &constructors); bool match = false; - for (const List<MethodInfo>::Element *E = constructors.front(); E != nullptr; E = E->next()) { - const MethodInfo &info = E->get(); - + for (const MethodInfo &info : constructors) { if (p_call->arguments.size() < info.arguments.size() - info.default_arguments.size()) { continue; } @@ -2059,9 +2319,11 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa if (p_call->is_super) { base_type = parser->current_class->base_type; + base_type.is_meta_type = false; is_self = true; } else if (callee_type == GDScriptParser::Node::IDENTIFIER) { base_type = parser->current_class->get_datatype(); + base_type.is_meta_type = false; is_self = true; } else if (callee_type == GDScriptParser::Node::SUBSCRIPT) { GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(p_call->callee); @@ -2078,9 +2340,23 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa mark_node_unsafe(p_call); return; } - reduce_expression(subscript->base); + if (subscript->attribute == nullptr) { + // Invalid call. Error already sent in parser. + p_call->set_datatype(call_type); + mark_node_unsafe(p_call); + return; + } - base_type = subscript->base->get_datatype(); + GDScriptParser::IdentifierNode *base_id = nullptr; + if (subscript->base->type == GDScriptParser::Node::IDENTIFIER) { + base_id = static_cast<GDScriptParser::IdentifierNode *>(subscript->base); + } + if (base_id && GDScriptParser::get_builtin_type(base_id->name) < Variant::VARIANT_MAX) { + base_type = make_builtin_meta_type(GDScriptParser::get_builtin_type(base_id->name)); + } else { + reduce_expression(subscript->base); + base_type = subscript->base->get_datatype(); + } } else { // Invalid call. Error already sent in parser. // TODO: Could check if Callable here too. @@ -2097,16 +2373,19 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa if (get_function_signature(p_call, base_type, p_call->function_name, return_type, par_types, default_arg_count, is_static, is_vararg)) { // If the function require typed arrays we must make literals be typed. - for (Map<int, GDScriptParser::ArrayNode *>::Element *E = arrays.front(); E; E = E->next()) { - int index = E->key(); + for (const KeyValue<int, GDScriptParser::ArrayNode *> &E : arrays) { + int index = E.key; if (index < par_types.size() && par_types[index].has_container_element_type()) { - update_array_literal_element_type(par_types[index], E->get()); + update_array_literal_element_type(par_types[index], E.value); } } validate_call_arg(par_types, default_arg_count, is_vararg, p_call); if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) { push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parser->current_function->identifier->name), p_call->callee); + } else if (!is_self && base_type.is_meta_type && !is_static) { + base_type.is_meta_type = false; // For `to_string()`. + push_error(vformat(R"*(Cannot call non-static function "%s()" on the class "%s" directly. Make an instance instead.)*", p_call->function_name, base_type.to_string()), p_call->callee); } else if (is_self && !is_static && !lambda_stack.is_empty()) { push_error(vformat(R"*(Cannot call non-static function "%s()" from a lambda function.)*", p_call->function_name), p_call->callee); } @@ -2147,7 +2426,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool is_awa } } - if (call_type.is_coroutine && !is_await) { + if (call_type.is_coroutine && !p_is_await && !p_is_root) { push_error(vformat(R"*(Function "%s()" is a coroutine, so it must be called with "await".)*", p_call->function_name), p_call->callee); } @@ -2229,7 +2508,7 @@ void GDScriptAnalyzer::reduce_get_node(GDScriptParser::GetNodeNode *p_get_node) result.native_type = "Node"; result.builtin_type = Variant::OBJECT; - if (!ClassDB::is_parent_class(GDScriptParser::get_real_class_name(parser->current_class->base_type.native_type), result.native_type)) { + if (!ClassDB::is_parent_class(parser->current_class->base_type.native_type, result.native_type)) { push_error(R"*(Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node.)*", p_get_node); } else if (!lambda_stack.is_empty()) { push_error(R"*(Cannot use shorthand "get_node()" notation ("$") inside a lambda. Use a captured variable instead.)*", p_get_node); @@ -2280,13 +2559,15 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod p_identifier->is_constant = true; p_identifier->reduced_value = result; p_identifier->set_datatype(type_from_variant(result, p_identifier)); - } else { + } else if (base.is_hard_type()) { push_error(vformat(R"(Cannot find constant "%s" on type "%s".)", name, base.to_string()), p_identifier); } } else { switch (base.builtin_type) { case Variant::NIL: { - push_error(vformat(R"(Invalid get index "%s" on base Nil)", name), p_identifier); + if (base.is_hard_type()) { + push_error(vformat(R"(Invalid get index "%s" on base Nil)", name), p_identifier); + } return; } case Variant::DICTIONARY: { @@ -2301,14 +2582,15 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod Variant::construct(base.builtin_type, dummy, nullptr, 0, temp); List<PropertyInfo> properties; dummy.get_property_list(&properties); - for (const List<PropertyInfo>::Element *E = properties.front(); E != nullptr; E = E->next()) { - const PropertyInfo &prop = E->get(); + for (const PropertyInfo &prop : properties) { if (prop.name == name) { p_identifier->set_datatype(type_from_property(prop)); return; } } - push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier); + if (base.is_hard_type()) { + push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier); + } } } } @@ -2324,11 +2606,15 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod GDScriptParser::DataType result; result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; result.kind = GDScriptParser::DataType::ENUM_VALUE; + result.builtin_type = base.builtin_type; result.native_type = base.native_type; result.enum_type = name; p_identifier->set_datatype(result); } else { - push_error(vformat(R"(Cannot find value "%s" in "%s".)", name, base.to_string()), p_identifier); + // Consider as a Dictionary + GDScriptParser::DataType dummy; + dummy.kind = GDScriptParser::DataType::VARIANT; + p_identifier->set_datatype(dummy); } } else { push_error(R"(Cannot get property from enum value.)", p_identifier); @@ -2365,11 +2651,17 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod case GDScriptParser::ClassNode::Member::VARIABLE: p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE; p_identifier->variable_source = member.variable; + member.variable->usages += 1; break; case GDScriptParser::ClassNode::Member::FUNCTION: resolve_function_signature(member.function); p_identifier->set_datatype(make_callable_type(member.function->info)); break; + case GDScriptParser::ClassNode::Member::CLASS: + // For out-of-order resolution: + resolve_class_interface(member.m_class); + p_identifier->set_datatype(member.m_class->get_datatype()); + break; default: break; // Type already set. } @@ -2381,14 +2673,34 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod while (outer != nullptr) { if (outer->has_member(name)) { const GDScriptParser::ClassNode::Member &member = outer->get_member(name); - if (member.type == GDScriptParser::ClassNode::Member::CONSTANT) { - // TODO: Make sure loops won't cause problem. And make special error message for those. - // For out-of-order resolution: - reduce_expression(member.constant->initializer); - p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = true; - p_identifier->reduced_value = member.constant->initializer->reduced_value; - return; + switch (member.type) { + case GDScriptParser::ClassNode::Member::CONSTANT: { + // TODO: Make sure loops won't cause problem. And make special error message for those. + // For out-of-order resolution: + reduce_expression(member.constant->initializer); + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.constant->initializer->reduced_value; + return; + } break; + case GDScriptParser::ClassNode::Member::ENUM_VALUE: { + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.enum_value.value; + return; + } break; + case GDScriptParser::ClassNode::Member::ENUM: { + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = false; + return; + } break; + case GDScriptParser::ClassNode::Member::CLASS: { + resolve_class_interface(member.m_class); + p_identifier->set_datatype(member.m_class->get_datatype()); + return; + } break; + default: + break; } } outer = outer->outer; @@ -2398,13 +2710,16 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } // Check native members. - const StringName &native = GDScriptParser::get_real_class_name(base.native_type); + const StringName &native = base.native_type; if (class_exists(native)) { - PropertyInfo prop_info; MethodInfo method_info; - if (ClassDB::get_property_info(native, name, &prop_info)) { - p_identifier->set_datatype(type_from_property(prop_info)); + if (ClassDB::has_property(native, name)) { + StringName getter_name = ClassDB::get_property_getter(native, name); + MethodBind *getter = ClassDB::get_method(native, getter_name); + if (getter != nullptr) { + p_identifier->set_datatype(type_from_property(getter->get_return_info())); + } return; } if (ClassDB::get_method_info(native, name, &method_info)) { @@ -2676,7 +2991,7 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { } else { p_preload->resolved_path = p_preload->path->reduced_value; // TODO: Save this as script dependency. - if (p_preload->resolved_path.is_rel_path()) { + if (p_preload->resolved_path.is_relative_path()) { p_preload->resolved_path = parser->script_path.get_base_dir().plus_file(p_preload->resolved_path); } p_preload->resolved_path = p_preload->resolved_path.simplify_path(); @@ -2686,7 +3001,7 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { // TODO: Don't load if validating: use completion cache. p_preload->resource = ResourceLoader::load(p_preload->resolved_path); if (p_preload->resource.is_null()) { - push_error(vformat(R"(Could not p_preload resource file "%s".)", p_preload->resolved_path), p_preload->path); + push_error(vformat(R"(Could not preload resource file "%s".)", p_preload->resolved_path), p_preload->path); } } } @@ -2702,6 +3017,9 @@ void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) { } void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscript) { + if (p_subscript->base == nullptr) { + return; + } if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER) { reduce_identifier(static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base), true); } else { @@ -2729,7 +3047,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri } else { GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); - if (base_type.is_variant()) { + if (base_type.is_variant() || !base_type.is_hard_type()) { result_type.kind = GDScriptParser::DataType::VARIANT; mark_node_unsafe(p_subscript); } else { @@ -2800,7 +3118,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::RECT2: case Variant::RECT2I: case Variant::PLANE: - case Variant::QUAT: + case Variant::QUATERNION: case Variant::AABB: case Variant::OBJECT: error = index_type.builtin_type != Variant::STRING; @@ -2811,8 +3129,8 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::VECTOR2I: case Variant::VECTOR3: case Variant::VECTOR3I: - case Variant::TRANSFORM: case Variant::TRANSFORM2D: + case Variant::TRANSFORM3D: error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT && index_type.builtin_type != Variant::STRING; break; @@ -2852,6 +3170,9 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri result_type.kind = GDScriptParser::DataType::BUILTIN; result_type.type_source = base_type.is_hard_type() ? GDScriptParser::DataType::ANNOTATED_INFERRED : GDScriptParser::DataType::INFERRED; + if (base_type.kind != GDScriptParser::DataType::BUILTIN) { + base_type.builtin_type = Variant::OBJECT; + } switch (base_type.builtin_type) { // Can't index at all. case Variant::RID: @@ -2879,7 +3200,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::PACKED_FLOAT64_ARRAY: case Variant::VECTOR2: case Variant::VECTOR3: - case Variant::QUAT: + case Variant::QUATERNION: result_type.builtin_type = Variant::FLOAT; break; // Return Color. @@ -2908,10 +3229,11 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri result_type.builtin_type = Variant::VECTOR3; break; // Depends on the index. - case Variant::TRANSFORM: + case Variant::TRANSFORM3D: case Variant::PLANE: case Variant::COLOR: case Variant::DICTIONARY: + case Variant::OBJECT: result_type.kind = GDScriptParser::DataType::VARIANT; result_type.type_source = GDScriptParser::DataType::UNDETECTED; break; @@ -2926,7 +3248,6 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri } break; // Here for completeness. - case Variant::OBJECT: case Variant::VARIANT_MAX: break; } @@ -3093,14 +3414,20 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va current = current->_owner; } - Ref<GDScriptParserRef> ref = get_parser_for(current->path); + Ref<GDScriptParserRef> ref = get_parser_for(current->get_path()); + if (ref.is_null()) { + push_error("Could not find script in path.", p_source); + GDScriptParser::DataType error_type; + error_type.kind = GDScriptParser::DataType::VARIANT; + return error_type; + } ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); GDScriptParser::ClassNode *found = ref->get_parser()->head; // It should be okay to assume this exists, since we have a complete script already. - for (const List<StringName>::Element *E = class_chain.front(); E; E = E->next()) { - found = found->get_member(E->get()).m_class; + for (const StringName &E : class_chain) { + found = found->get_member(E).m_class; } result.class_type = found; @@ -3173,13 +3500,14 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo } else { ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed array."); } + elem_type.is_constant = false; result.set_container_element_type(elem_type); } } return result; } -bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) { +bool GDScriptAnalyzer::get_function_signature(GDScriptParser::CallNode *p_source, GDScriptParser::DataType p_base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg) { r_static = false; r_vararg = false; r_default_arg_count = 0; @@ -3196,16 +3524,18 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD List<MethodInfo> methods; dummy.get_method_list(&methods); - for (const List<MethodInfo>::Element *E = methods.front(); E != nullptr; E = E->next()) { - if (E->get().name == p_function) { - return function_signature_from_info(E->get(), r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + for (const MethodInfo &E : methods) { + if (E.name == p_function) { + function_signature_from_info(E, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + r_static = Variant::is_builtin_method_static(p_base_type.builtin_type, function_name); + return true; } } return false; } - bool is_constructor = p_base_type.is_meta_type && p_function == "new"; + bool is_constructor = (p_base_type.is_meta_type || (p_source->callee && p_source->callee->type == GDScriptParser::Node::IDENTIFIER)) && p_function == StaticCString::create("new"); if (is_constructor) { function_name = "_init"; r_static = true; @@ -3235,6 +3565,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD } } r_return_type = found_function->get_datatype(); + r_return_type.is_meta_type = false; r_return_type.is_coroutine = found_function->is_coroutine; return true; @@ -3280,11 +3611,13 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, GD return true; } - StringName real_native = GDScriptParser::get_real_class_name(base_native); - MethodInfo info; - if (ClassDB::get_method_info(real_native, function_name, &info)) { - return function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + if (ClassDB::get_method_info(base_native, function_name, &info)) { + bool valid = function_signature_from_info(info, r_return_type, r_par_types, r_default_arg_count, r_static, r_vararg); + if (valid && Engine::get_singleton()->has_singleton(base_native)) { + r_static = true; + } + return valid; } return false; @@ -3295,8 +3628,8 @@ bool GDScriptAnalyzer::function_signature_from_info(const MethodInfo &p_info, GD r_default_arg_count = p_info.default_arguments.size(); r_vararg = (p_info.flags & METHOD_FLAG_VARARG) != 0; - for (const List<PropertyInfo>::Element *E = p_info.arguments.front(); E != nullptr; E = E->next()) { - r_par_types.push_back(type_from_property(E->get())); + for (const PropertyInfo &E : p_info.arguments) { + r_par_types.push_back(type_from_property(E)); } return true; } @@ -3304,8 +3637,8 @@ bool GDScriptAnalyzer::function_signature_from_info(const MethodInfo &p_info, GD bool GDScriptAnalyzer::validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call) { List<GDScriptParser::DataType> arg_types; - for (const List<PropertyInfo>::Element *E = p_method.arguments.front(); E != nullptr; E = E->next()) { - arg_types.push_back(type_from_property(E->get())); + for (const PropertyInfo &E : p_method.arguments) { + arg_types.push_back(type_from_property(E)); } return validate_call_arg(arg_types, p_method.default_arguments.size(), (p_method.flags & METHOD_FLAG_VARARG) != 0, p_call); @@ -3375,24 +3708,23 @@ bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con StringName parent = base_native; while (parent != StringName()) { - StringName real_class_name = GDScriptParser::get_real_class_name(parent); - if (ClassDB::has_method(real_class_name, name, true)) { + if (ClassDB::has_method(parent, name, true)) { parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "method", parent); return true; - } else if (ClassDB::has_signal(real_class_name, name, true)) { + } else if (ClassDB::has_signal(parent, name, true)) { parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "signal", parent); return true; - } else if (ClassDB::has_property(real_class_name, name, true)) { + } else if (ClassDB::has_property(parent, name, true)) { parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "property", parent); return true; - } else if (ClassDB::has_integer_constant(real_class_name, name, true)) { + } else if (ClassDB::has_integer_constant(parent, name, true)) { parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "constant", parent); return true; - } else if (ClassDB::has_enum(real_class_name, name, true)) { + } else if (ClassDB::has_enum(parent, name, true)) { parser->push_warning(p_local, GDScriptWarning::SHADOWED_VARIABLE_BASE_CLASS, p_context, p_local->name, "enum", parent); return true; } - parent = ClassDB::get_parent_class(real_class_name); + parent = ClassDB::get_parent_class(parent); } return false; @@ -3421,6 +3753,7 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator } r_valid = true; + result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = Variant::get_operator_return_type(p_operation, a_type, b_type); @@ -3472,6 +3805,11 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) { return true; } + if (p_source.kind == GDScriptParser::DataType::ENUM) { + if (p_source.native_type == p_target.native_type) { + return true; + } + } if (p_source.kind == GDScriptParser::DataType::ENUM_VALUE) { if (p_source.native_type == p_target.native_type && p_target.enum_values.has(p_source.enum_type)) { return true; @@ -3536,16 +3874,12 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ break; // Already solved before. } - // Get underscore-prefixed version for some classes. - src_native = GDScriptParser::get_real_class_name(src_native); - switch (p_target.kind) { case GDScriptParser::DataType::NATIVE: { if (p_target.is_meta_type) { return ClassDB::is_parent_class(src_native, GDScriptNativeClass::get_class_static()); } - StringName tgt_native = GDScriptParser::get_real_class_name(p_target.native_type); - return ClassDB::is_parent_class(src_native, tgt_native); + return ClassDB::is_parent_class(src_native, p_target.native_type); } case GDScriptParser::DataType::SCRIPT: if (p_target.is_meta_type) { @@ -3594,8 +3928,7 @@ void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) { } bool GDScriptAnalyzer::class_exists(const StringName &p_class) const { - StringName real_name = GDScriptParser::get_real_class_name(p_class); - return ClassDB::class_exists(real_name) && ClassDB::is_class_exposed(real_name); + return ClassDB::class_exists(p_class) && ClassDB::is_class_exposed(p_class); } Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) { @@ -3631,11 +3964,11 @@ Error GDScriptAnalyzer::resolve_program() { List<String> parser_keys; depended_parsers.get_key_list(&parser_keys); - for (const List<String>::Element *E = parser_keys.front(); E != nullptr; E = E->next()) { - if (depended_parsers[E->get()].is_null()) { + for (const String &E : parser_keys) { + if (depended_parsers[E].is_null()) { return ERR_PARSE_ERROR; } - depended_parsers[E->get()]->raise_status(GDScriptParserRef::FULLY_SOLVED); + depended_parsers[E]->raise_status(GDScriptParserRef::FULLY_SOLVED); } return parser->errors.is_empty() ? OK : ERR_PARSE_ERROR; } diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index aabf407c76..ce4525190b 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -32,7 +32,7 @@ #define GDSCRIPT_ANALYZER_H #include "core/object/object.h" -#include "core/object/reference.h" +#include "core/object/ref_counted.h" #include "core/templates/set.h" #include "gdscript_cache.h" #include "gdscript_parser.h" @@ -44,6 +44,12 @@ class GDScriptAnalyzer { const GDScriptParser::EnumNode *current_enum = nullptr; List<const GDScriptParser::LambdaNode *> lambda_stack; + // Tests for detecting invalid overloading of script members + static _FORCE_INLINE_ bool has_member_name_conflict_in_script_class(const StringName &p_name, const GDScriptParser::ClassNode *p_current_class_node); + static _FORCE_INLINE_ bool has_member_name_conflict_in_native_type(const StringName &p_name, const StringName &p_native_type_string); + Error check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string); + Error check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node); + Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true); GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type); @@ -72,12 +78,12 @@ class GDScriptAnalyzer { void resolve_return(GDScriptParser::ReturnNode *p_return); // Reduction functions. - void reduce_expression(GDScriptParser::ExpressionNode *p_expression); + void reduce_expression(GDScriptParser::ExpressionNode *p_expression, bool p_is_root = false); void reduce_array(GDScriptParser::ArrayNode *p_array); void reduce_assignment(GDScriptParser::AssignmentNode *p_assignment); void reduce_await(GDScriptParser::AwaitNode *p_await); void reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_op); - void reduce_call(GDScriptParser::CallNode *p_call, bool is_await = false); + void reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await = false, bool p_is_root = false); void reduce_cast(GDScriptParser::CastNode *p_cast); void reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary); void reduce_get_node(GDScriptParser::GetNodeNode *p_get_node); @@ -99,7 +105,7 @@ class GDScriptAnalyzer { GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type) const; GDScriptParser::DataType type_from_property(const PropertyInfo &p_property) const; GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source); - bool get_function_signature(GDScriptParser::Node *p_source, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); + bool get_function_signature(GDScriptParser::CallNode *p_source, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); bool validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call); bool validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 0da99ccee3..6a7e4278d2 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -85,10 +85,10 @@ uint32_t GDScriptByteCodeGenerator::add_temporary(const GDScriptDataType &p_type case Variant::VECTOR3I: case Variant::TRANSFORM2D: case Variant::PLANE: - case Variant::QUAT: + case Variant::QUATERNION: case Variant::AABB: case Variant::BASIS: - case Variant::TRANSFORM: + case Variant::TRANSFORM3D: case Variant::COLOR: case Variant::STRING_NAME: case Variant::NODE_PATH: @@ -129,12 +129,6 @@ uint32_t GDScriptByteCodeGenerator::add_temporary(const GDScriptDataType &p_type int idx = temporaries.size(); pool.push_back(idx); temporaries.push_back(new_temp); - - // First time using this, so adjust to the proper type. - if (temp_type != Variant::NIL) { - Address addr(Address::TEMPORARY, idx, p_type); - write_type_adjust(addr, temp_type); - } } int slot = pool.front()->get(); pool.pop_front(); @@ -161,7 +155,7 @@ void GDScriptByteCodeGenerator::end_parameters() { function->default_arguments.reverse(); } -void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCMode p_rpc_mode, const GDScriptDataType &p_return_type) { +void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) { function = memnew(GDScriptFunction); debug_stack = EngineDebugger::is_active(); @@ -176,7 +170,7 @@ void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName function->_static = p_static; function->return_type = p_return_type; - function->rpc_mode = p_rpc_mode; + function->rpc_config = p_rpc_config; function->_argument_count = 0; } @@ -189,8 +183,12 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { append(GDScriptFunction::OPCODE_END, 0); for (int i = 0; i < temporaries.size(); i++) { + int stack_index = i + max_locals + RESERVED_STACK; for (int j = 0; j < temporaries[i].bytecode_indices.size(); j++) { - opcodes.write[temporaries[i].bytecode_indices[j]] = (i + max_locals + RESERVED_STACK) | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); + opcodes.write[temporaries[i].bytecode_indices[j]] = stack_index | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); + } + if (temporaries[i].type != Variant::NIL) { + function->temporary_slots[stack_index] = temporaries[i].type; } } @@ -211,8 +209,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { if (name_map.size()) { function->global_names.resize(name_map.size()); function->_global_names_ptr = &function->global_names[0]; - for (Map<StringName, int>::Element *E = name_map.front(); E; E = E->next()) { - function->global_names.write[E->get()] = E->key(); + for (const KeyValue<StringName, int> &E : name_map) { + function->global_names.write[E.value] = E.key; } function->_global_names_count = function->global_names.size(); @@ -243,8 +241,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->operator_funcs.resize(operator_func_map.size()); function->_operator_funcs_count = function->operator_funcs.size(); function->_operator_funcs_ptr = function->operator_funcs.ptr(); - for (const Map<Variant::ValidatedOperatorEvaluator, int>::Element *E = operator_func_map.front(); E; E = E->next()) { - function->operator_funcs.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedOperatorEvaluator, int> &E : operator_func_map) { + function->operator_funcs.write[E.value] = E.key; } } else { function->_operator_funcs_count = 0; @@ -255,8 +253,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->setters.resize(setters_map.size()); function->_setters_count = function->setters.size(); function->_setters_ptr = function->setters.ptr(); - for (const Map<Variant::ValidatedSetter, int>::Element *E = setters_map.front(); E; E = E->next()) { - function->setters.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedSetter, int> &E : setters_map) { + function->setters.write[E.value] = E.key; } } else { function->_setters_count = 0; @@ -267,8 +265,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->getters.resize(getters_map.size()); function->_getters_count = function->getters.size(); function->_getters_ptr = function->getters.ptr(); - for (const Map<Variant::ValidatedGetter, int>::Element *E = getters_map.front(); E; E = E->next()) { - function->getters.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedGetter, int> &E : getters_map) { + function->getters.write[E.value] = E.key; } } else { function->_getters_count = 0; @@ -279,8 +277,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->keyed_setters.resize(keyed_setters_map.size()); function->_keyed_setters_count = function->keyed_setters.size(); function->_keyed_setters_ptr = function->keyed_setters.ptr(); - for (const Map<Variant::ValidatedKeyedSetter, int>::Element *E = keyed_setters_map.front(); E; E = E->next()) { - function->keyed_setters.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedKeyedSetter, int> &E : keyed_setters_map) { + function->keyed_setters.write[E.value] = E.key; } } else { function->_keyed_setters_count = 0; @@ -291,8 +289,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->keyed_getters.resize(keyed_getters_map.size()); function->_keyed_getters_count = function->keyed_getters.size(); function->_keyed_getters_ptr = function->keyed_getters.ptr(); - for (const Map<Variant::ValidatedKeyedGetter, int>::Element *E = keyed_getters_map.front(); E; E = E->next()) { - function->keyed_getters.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedKeyedGetter, int> &E : keyed_getters_map) { + function->keyed_getters.write[E.value] = E.key; } } else { function->_keyed_getters_count = 0; @@ -303,8 +301,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->indexed_setters.resize(indexed_setters_map.size()); function->_indexed_setters_count = function->indexed_setters.size(); function->_indexed_setters_ptr = function->indexed_setters.ptr(); - for (const Map<Variant::ValidatedIndexedSetter, int>::Element *E = indexed_setters_map.front(); E; E = E->next()) { - function->indexed_setters.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedIndexedSetter, int> &E : indexed_setters_map) { + function->indexed_setters.write[E.value] = E.key; } } else { function->_indexed_setters_count = 0; @@ -315,8 +313,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->indexed_getters.resize(indexed_getters_map.size()); function->_indexed_getters_count = function->indexed_getters.size(); function->_indexed_getters_ptr = function->indexed_getters.ptr(); - for (const Map<Variant::ValidatedIndexedGetter, int>::Element *E = indexed_getters_map.front(); E; E = E->next()) { - function->indexed_getters.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedIndexedGetter, int> &E : indexed_getters_map) { + function->indexed_getters.write[E.value] = E.key; } } else { function->_indexed_getters_count = 0; @@ -327,8 +325,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->builtin_methods.resize(builtin_method_map.size()); function->_builtin_methods_ptr = function->builtin_methods.ptr(); function->_builtin_methods_count = builtin_method_map.size(); - for (const Map<Variant::ValidatedBuiltInMethod, int>::Element *E = builtin_method_map.front(); E; E = E->next()) { - function->builtin_methods.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedBuiltInMethod, int> &E : builtin_method_map) { + function->builtin_methods.write[E.value] = E.key; } } else { function->_builtin_methods_ptr = nullptr; @@ -339,8 +337,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->constructors.resize(constructors_map.size()); function->_constructors_ptr = function->constructors.ptr(); function->_constructors_count = constructors_map.size(); - for (const Map<Variant::ValidatedConstructor, int>::Element *E = constructors_map.front(); E; E = E->next()) { - function->constructors.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedConstructor, int> &E : constructors_map) { + function->constructors.write[E.value] = E.key; } } else { function->_constructors_ptr = nullptr; @@ -351,8 +349,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->utilities.resize(utilities_map.size()); function->_utilities_ptr = function->utilities.ptr(); function->_utilities_count = utilities_map.size(); - for (const Map<Variant::ValidatedUtilityFunction, int>::Element *E = utilities_map.front(); E; E = E->next()) { - function->utilities.write[E->get()] = E->key(); + for (const KeyValue<Variant::ValidatedUtilityFunction, int> &E : utilities_map) { + function->utilities.write[E.value] = E.key; } } else { function->_utilities_ptr = nullptr; @@ -363,8 +361,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->gds_utilities.resize(gds_utilities_map.size()); function->_gds_utilities_ptr = function->gds_utilities.ptr(); function->_gds_utilities_count = gds_utilities_map.size(); - for (const Map<GDScriptUtilityFunctions::FunctionPtr, int>::Element *E = gds_utilities_map.front(); E; E = E->next()) { - function->gds_utilities.write[E->get()] = E->key(); + for (const KeyValue<GDScriptUtilityFunctions::FunctionPtr, int> &E : gds_utilities_map) { + function->gds_utilities.write[E.value] = E.key; } } else { function->_gds_utilities_ptr = nullptr; @@ -375,8 +373,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->methods.resize(method_bind_map.size()); function->_methods_ptr = function->methods.ptrw(); function->_methods_count = method_bind_map.size(); - for (const Map<MethodBind *, int>::Element *E = method_bind_map.front(); E; E = E->next()) { - function->methods.write[E->get()] = E->key(); + for (const KeyValue<MethodBind *, int> &E : method_bind_map) { + function->methods.write[E.value] = E.key; } } else { function->_methods_ptr = nullptr; @@ -387,8 +385,8 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->lambdas.resize(lambdas_map.size()); function->_lambdas_ptr = function->lambdas.ptrw(); function->_lambdas_count = lambdas_map.size(); - for (const Map<GDScriptFunction *, int>::Element *E = lambdas_map.front(); E; E = E->next()) { - function->lambdas.write[E->get()] = E->key(); + for (const KeyValue<GDScriptFunction *, int> &E : lambdas_map) { + function->lambdas.write[E.value] = E.key; } } else { function->_lambdas_ptr = nullptr; @@ -460,8 +458,8 @@ void GDScriptByteCodeGenerator::write_type_adjust(const Address &p_target, Varia case Variant::PLANE: append(GDScriptFunction::OPCODE_TYPE_ADJUST_PLANE, 1); break; - case Variant::QUAT: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_QUAT, 1); + case Variant::QUATERNION: + append(GDScriptFunction::OPCODE_TYPE_ADJUST_QUATERNION, 1); break; case Variant::AABB: append(GDScriptFunction::OPCODE_TYPE_ADJUST_AABB, 1); @@ -469,7 +467,7 @@ void GDScriptByteCodeGenerator::write_type_adjust(const Address &p_target, Varia case Variant::BASIS: append(GDScriptFunction::OPCODE_TYPE_ADJUST_BASIS, 1); break; - case Variant::TRANSFORM: + case Variant::TRANSFORM3D: append(GDScriptFunction::OPCODE_TYPE_ADJUST_TRANSFORM, 1); break; case Variant::COLOR: @@ -556,6 +554,14 @@ void GDScriptByteCodeGenerator::write_unary_operator(const Address &p_target, Va void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, Variant::Operator p_operator, const Address &p_left_operand, const Address &p_right_operand) { if (HAS_BUILTIN_TYPE(p_left_operand) && HAS_BUILTIN_TYPE(p_right_operand)) { + if (p_target.mode == Address::TEMPORARY) { + Variant::Type result_type = Variant::get_operator_return_type(p_operator, p_left_operand.type.builtin_type, p_right_operand.type.builtin_type); + Variant::Type temp_type = temporaries[p_target.address].type; + if (result_type != temp_type) { + write_type_adjust(p_target, result_type); + } + } + // Gather specific operator. Variant::ValidatedOperatorEvaluator op_func = Variant::get_validated_operator_evaluator(p_operator, p_left_operand.type.builtin_type, p_right_operand.type.builtin_type); @@ -686,7 +692,8 @@ void GDScriptByteCodeGenerator::write_end_ternary() { void GDScriptByteCodeGenerator::write_set(const Address &p_target, const Address &p_index, const Address &p_source) { if (HAS_BUILTIN_TYPE(p_target)) { - if (IS_BUILTIN_TYPE(p_index, Variant::INT) && Variant::get_member_validated_indexed_setter(p_target.type.builtin_type)) { + if (IS_BUILTIN_TYPE(p_index, Variant::INT) && Variant::get_member_validated_indexed_setter(p_target.type.builtin_type) && + IS_BUILTIN_TYPE(p_source, Variant::get_indexed_element_type(p_target.type.builtin_type))) { // Use indexed setter instead. Variant::ValidatedIndexedSetter setter = Variant::get_member_validated_indexed_setter(p_target.type.builtin_type); append(GDScriptFunction::OPCODE_SET_INDEXED_VALIDATED, 3); @@ -740,7 +747,8 @@ void GDScriptByteCodeGenerator::write_get(const Address &p_target, const Address } void GDScriptByteCodeGenerator::write_set_named(const Address &p_target, const StringName &p_name, const Address &p_source) { - if (HAS_BUILTIN_TYPE(p_target) && Variant::get_member_validated_setter(p_target.type.builtin_type, p_name)) { + if (HAS_BUILTIN_TYPE(p_target) && Variant::get_member_validated_setter(p_target.type.builtin_type, p_name) && + IS_BUILTIN_TYPE(p_source, Variant::get_member_type(p_target.type.builtin_type, p_name))) { Variant::ValidatedSetter setter = Variant::get_member_validated_setter(p_target.type.builtin_type, p_name); append(GDScriptFunction::OPCODE_SET_NAMED_VALIDATED, 2); append(p_target); @@ -781,63 +789,43 @@ void GDScriptByteCodeGenerator::write_get_member(const Address &p_target, const append(p_name); } -void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Address &p_source) { - if (p_target.type.has_type && !p_source.type.has_type) { - // Typed assignment. - switch (p_target.type.kind) { - case GDScriptDataType::BUILTIN: { - if (p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) { - append(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY, 2); - append(p_target); - append(p_source); - } else { - append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2); - append(p_target); - append(p_source); - append(p_target.type.builtin_type); - } - } break; - case GDScriptDataType::NATIVE: { - int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_target.type.native_type]; - Variant nc = GDScriptLanguage::get_singleton()->get_global_array()[class_idx]; - class_idx = get_constant_pos(nc) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); - append(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE, 3); - append(p_target); - append(p_source); - append(class_idx); - } break; - case GDScriptDataType::SCRIPT: - case GDScriptDataType::GDSCRIPT: { - Variant script = p_target.type.script_type; - int idx = get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); - - append(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT, 3); +void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_target, const Address &p_source) { + switch (p_target.type.kind) { + case GDScriptDataType::BUILTIN: { + if (p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) { + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY, 2); append(p_target); append(p_source); - append(idx); - } break; - default: { - ERR_PRINT("Compiler bug: unresolved assign."); - - // Shouldn't get here, but fail-safe to a regular assignment - append(GDScriptFunction::OPCODE_ASSIGN, 2); + } else { + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2); append(p_target); append(p_source); + append(p_target.type.builtin_type); } - } - } else { - if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) { - append(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY, 2); + } break; + case GDScriptDataType::NATIVE: { + int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_target.type.native_type]; + Variant nc = GDScriptLanguage::get_singleton()->get_global_array()[class_idx]; + class_idx = get_constant_pos(nc) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE, 3); append(p_target); append(p_source); - } else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) { - // Need conversion.. - append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2); + append(class_idx); + } break; + case GDScriptDataType::SCRIPT: + case GDScriptDataType::GDSCRIPT: { + Variant script = p_target.type.script_type; + int idx = get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); + + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT, 3); append(p_target); append(p_source); - append(p_target.type.builtin_type); - } else { - // Either untyped assignment or already type-checked by the parser + append(idx); + } break; + default: { + ERR_PRINT("Compiler bug: unresolved assign."); + + // Shouldn't get here, but fail-safe to a regular assignment append(GDScriptFunction::OPCODE_ASSIGN, 2); append(p_target); append(p_source); @@ -845,6 +833,24 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr } } +void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Address &p_source) { + if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::ARRAY && p_target.type.has_container_element_type()) { + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY, 2); + append(p_target); + append(p_source); + } else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) { + // Need conversion. + append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2); + append(p_target); + append(p_source); + append(p_target.type.builtin_type); + } else { + append(GDScriptFunction::OPCODE_ASSIGN, 2); + append(p_target); + append(p_source); + } +} + void GDScriptByteCodeGenerator::write_assign_true(const Address &p_target) { append(GDScriptFunction::OPCODE_ASSIGN_TRUE, 1); append(p_target); @@ -860,6 +866,12 @@ void GDScriptByteCodeGenerator::write_assign_default_parameter(const Address &p_ function->default_arguments.push_back(opcodes.size()); } +void GDScriptByteCodeGenerator::write_store_global(const Address &p_dst, int p_global_index) { + append(GDScriptFunction::OPCODE_STORE_GLOBAL, 1); + append(p_dst); + append(p_global_index); +} + void GDScriptByteCodeGenerator::write_store_named_global(const Address &p_dst, const StringName &p_global) { append(GDScriptFunction::OPCODE_STORE_NAMED_GLOBAL, 1); append(p_dst); @@ -1017,6 +1029,56 @@ void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target, append(Variant::get_validated_builtin_method(p_type, p_method)); } +void GDScriptByteCodeGenerator::write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) { + bool is_validated = false; + + // Check if all types are correct. + if (Variant::is_builtin_method_vararg(p_type, p_method)) { + is_validated = true; // Vararg works fine with any argument, since they can be any type. + } else if (p_arguments.size() == Variant::get_builtin_method_argument_count(p_type, p_method)) { + bool all_types_exact = true; + for (int i = 0; i < p_arguments.size(); i++) { + if (!IS_BUILTIN_TYPE(p_arguments[i], Variant::get_builtin_method_argument_type(p_type, p_method, i))) { + all_types_exact = false; + break; + } + } + + is_validated = all_types_exact; + } + + if (!is_validated) { + // Perform regular call. + append(GDScriptFunction::OPCODE_CALL_BUILTIN_STATIC, p_arguments.size() + 1); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(p_target); + append(p_type); + append(p_method); + append(p_arguments.size()); + return; + } + + if (p_target.mode == Address::TEMPORARY) { + Variant::Type result_type = Variant::get_builtin_method_return_type(p_type, p_method); + Variant::Type temp_type = temporaries[p_target.address].type; + if (result_type != temp_type) { + write_type_adjust(p_target, result_type); + } + } + + append(GDScriptFunction::OPCODE_CALL_BUILTIN_TYPE_VALIDATED, 2 + p_arguments.size()); + + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(Address()); // No base since it's static. + append(p_target); + append(p_arguments.size()); + append(Variant::get_validated_builtin_method(p_type, p_method)); +} + void GDScriptByteCodeGenerator::write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) { append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL_METHOD_BIND : GDScriptFunction::OPCODE_CALL_METHOD_BIND_RET, 2 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { @@ -1054,12 +1116,12 @@ void GDScriptByteCodeGenerator::write_call_ptrcall(const Address &p_target, cons CASE_TYPE(PLANE); CASE_TYPE(AABB); CASE_TYPE(BASIS); - CASE_TYPE(TRANSFORM); + CASE_TYPE(TRANSFORM3D); CASE_TYPE(COLOR); CASE_TYPE(STRING_NAME); CASE_TYPE(NODE_PATH); CASE_TYPE(RID); - CASE_TYPE(QUAT); + CASE_TYPE(QUATERNION); CASE_TYPE(OBJECT); CASE_TYPE(CALLABLE); CASE_TYPE(SIGNAL); @@ -1405,8 +1467,8 @@ void GDScriptByteCodeGenerator::write_endfor() { } // Patch break statements. - for (const List<int>::Element *E = current_breaks_to_patch.back()->get().front(); E; E = E->next()) { - patch_jump(E->get()); + for (const int &E : current_breaks_to_patch.back()->get()) { + patch_jump(E); } current_breaks_to_patch.pop_back(); @@ -1440,8 +1502,8 @@ void GDScriptByteCodeGenerator::write_endwhile() { while_jmp_addrs.pop_back(); // Patch break statements. - for (const List<int>::Element *E = current_breaks_to_patch.back()->get().front(); E; E = E->next()) { - patch_jump(E->get()); + for (const int &E : current_breaks_to_patch.back()->get()) { + patch_jump(E); } current_breaks_to_patch.pop_back(); } @@ -1452,8 +1514,8 @@ void GDScriptByteCodeGenerator::start_match() { void GDScriptByteCodeGenerator::start_match_branch() { // Patch continue statements. - for (const List<int>::Element *E = match_continues_to_patch.back()->get().front(); E; E = E->next()) { - patch_jump(E->get()); + for (const int &E : match_continues_to_patch.back()->get()) { + patch_jump(E); } match_continues_to_patch.pop_back(); // Start a new list for next branch. @@ -1462,8 +1524,8 @@ void GDScriptByteCodeGenerator::start_match_branch() { void GDScriptByteCodeGenerator::end_match() { // Patch continue statements. - for (const List<int>::Element *E = match_continues_to_patch.back()->get().front(); E; E = E->next()) { - patch_jump(E->get()); + for (const int &E : match_continues_to_patch.back()->get()) { + patch_jump(E); } match_continues_to_patch.pop_back(); } diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index c060476f39..fbbf5802fd 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -153,12 +153,12 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { #endif locals.resize(current_locals); if (debug_stack) { - for (Map<StringName, int>::Element *E = block_identifiers.front(); E; E = E->next()) { + for (const KeyValue<StringName, int> &E : block_identifiers) { GDScriptFunction::StackDebug sd; sd.added = false; - sd.identifier = E->key(); + sd.identifier = E.key; sd.line = current_line; - sd.pos = E->get(); + sd.pos = E.value; stack_debug.push_back(sd); } block_identifiers = block_identifier_stack.back()->get(); @@ -419,7 +419,7 @@ public: virtual void start_block() override; virtual void end_block() override; - virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCMode p_rpc_mode, const GDScriptDataType &p_return_type) override; + virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) override; virtual GDScriptFunction *write_end() override; #ifdef DEBUG_ENABLED @@ -450,9 +450,11 @@ public: virtual void write_set_member(const Address &p_value, const StringName &p_name) override; virtual void write_get_member(const Address &p_target, const StringName &p_name) override; virtual void write_assign(const Address &p_target, const Address &p_source) override; + virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) override; virtual void write_assign_true(const Address &p_target) override; virtual void write_assign_false(const Address &p_target) override; virtual void write_assign_default_parameter(const Address &p_dst, const Address &p_src) override; + virtual void write_store_global(const Address &p_dst, int p_global_index) override; virtual void write_store_named_global(const Address &p_dst, const StringName &p_global) override; virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) override; virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; @@ -461,6 +463,7 @@ public: virtual void write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) override; virtual void write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) override; virtual void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override; + virtual void write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) override; virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override; virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) override; virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override; diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 113d36be98..bb0d9e9e9b 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -30,7 +30,7 @@ #include "gdscript_cache.h" -#include "core/os/file_access.h" +#include "core/io/file_access.h" #include "core/templates/vector.h" #include "gdscript.h" #include "gdscript_analyzer.h" @@ -51,32 +51,34 @@ GDScriptParser *GDScriptParserRef::get_parser() const { Error GDScriptParserRef::raise_status(Status p_new_status) { ERR_FAIL_COND_V(parser == nullptr, ERR_INVALID_DATA); - Error result = OK; + if (result != OK) { + return result; + } while (p_new_status > status) { switch (status) { case EMPTY: - result = parser->parse(GDScriptCache::get_source_code(path), path, false); status = PARSED; + result = parser->parse(GDScriptCache::get_source_code(path), path, false); break; case PARSED: { analyzer = memnew(GDScriptAnalyzer(parser)); - Error inheritance_result = analyzer->resolve_inheritance(); status = INHERITANCE_SOLVED; + Error inheritance_result = analyzer->resolve_inheritance(); if (result == OK) { result = inheritance_result; } } break; case INHERITANCE_SOLVED: { - Error interface_result = analyzer->resolve_interface(); status = INTERFACE_SOLVED; + Error interface_result = analyzer->resolve_interface(); if (result == OK) { result = interface_result; } } break; case INTERFACE_SOLVED: { - Error body_result = analyzer->resolve_body(); status = FULLY_SOLVED; + Error body_result = analyzer->resolve_body(); if (result == OK) { result = body_result; } @@ -86,14 +88,6 @@ Error GDScriptParserRef::raise_status(Status p_new_status) { } } if (result != OK) { - if (parser != nullptr) { - memdelete(parser); - parser = nullptr; - } - if (analyzer != nullptr) { - memdelete(analyzer); - analyzer = nullptr; - } return result; } } @@ -134,7 +128,7 @@ Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptP return ref; } GDScriptParser *parser = memnew(GDScriptParser); - ref.instance(); + ref.instantiate(); ref->parser = parser; ref->path = p_path; singleton->parser_map[p_path] = ref.ptr(); @@ -153,9 +147,9 @@ String GDScriptCache::get_source_code(const String &p_path) { ERR_FAIL_COND_V(err, ""); } - int len = f->get_len(); + uint64_t len = f->get_length(); source_file.resize(len + 1); - int r = f->get_buffer(source_file.ptrw(), len); + uint64_t r = f->get_buffer(source_file.ptrw(), len); f->close(); ERR_FAIL_COND_V(r != len, ""); source_file.write[len] = 0; @@ -180,7 +174,7 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const Stri } Ref<GDScript> script; - script.instance(); + script.instantiate(); script->set_path(p_path, true); script->set_script_path(p_path); script->load_source_code(p_path); @@ -200,7 +194,9 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro if (singleton->full_gdscript_cache.has(p_path)) { return singleton->full_gdscript_cache[p_path]; } + Ref<GDScript> script = get_shallow_script(p_path); + ERR_FAIL_COND_V(script.is_null(), Ref<GDScript>()); r_error = script->load_source_code(p_path); diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index d1d2a2abbf..9fb661d031 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -31,7 +31,7 @@ #ifndef GDSCRIPT_CACHE_H #define GDSCRIPT_CACHE_H -#include "core/object/reference.h" +#include "core/object/ref_counted.h" #include "core/os/mutex.h" #include "core/templates/hash_map.h" #include "core/templates/set.h" @@ -40,7 +40,7 @@ class GDScriptAnalyzer; class GDScriptParser; -class GDScriptParserRef : public Reference { +class GDScriptParserRef : public RefCounted { public: enum Status { EMPTY, @@ -54,6 +54,7 @@ private: GDScriptParser *parser = nullptr; GDScriptAnalyzer *analyzer = nullptr; Status status = EMPTY; + Error result = OK; String path; friend class GDScriptCache; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index ae9a8ede5e..e6ecc92d55 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -31,7 +31,7 @@ #ifndef GDSCRIPT_CODEGEN #define GDSCRIPT_CODEGEN -#include "core/io/multiplayer_api.h" +#include "core/multiplayer/multiplayer.h" #include "core/string/string_name.h" #include "core/variant/variant.h" #include "gdscript_function.h" @@ -80,7 +80,7 @@ public: virtual void start_block() = 0; virtual void end_block() = 0; - virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, MultiplayerAPI::RPCMode p_rpc_mode, const GDScriptDataType &p_return_type) = 0; + virtual void write_start(GDScript *p_script, const StringName &p_function_name, bool p_static, Multiplayer::RPCConfig p_rpc_config, const GDScriptDataType &p_return_type) = 0; virtual GDScriptFunction *write_end() = 0; #ifdef DEBUG_ENABLED @@ -111,9 +111,11 @@ public: virtual void write_set_member(const Address &p_value, const StringName &p_name) = 0; virtual void write_get_member(const Address &p_target, const StringName &p_name) = 0; virtual void write_assign(const Address &p_target, const Address &p_source) = 0; + virtual void write_assign_with_conversion(const Address &p_target, const Address &p_source) = 0; virtual void write_assign_true(const Address &p_target) = 0; virtual void write_assign_false(const Address &p_target) = 0; virtual void write_assign_default_parameter(const Address &dst, const Address &src) = 0; + virtual void write_store_global(const Address &p_dst, int p_global_index) = 0; virtual void write_store_named_global(const Address &p_dst, const StringName &p_global) = 0; virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) = 0; virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; @@ -122,6 +124,7 @@ public: virtual void write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) = 0; virtual void write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) = 0; virtual void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) = 0; + virtual void write_call_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) = 0; virtual void write_call_method_bind(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0; virtual void write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) = 0; virtual void write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 37ce8ae2cb..ab0fe5c37d 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -35,6 +35,8 @@ #include "gdscript_cache.h" #include "gdscript_utility_functions.h" +#include "core/config/project_settings.h" + bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringName &p_name) { if (codegen.function_node && codegen.function_node->is_static) { return false; @@ -107,7 +109,9 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D // Locate class by constructing the path to it and following that path GDScriptParser::ClassNode *class_type = p_datatype.class_type; if (class_type) { - if (class_type->fqcn.begins_with(main_script->path) || (!main_script->name.is_empty() && class_type->fqcn.begins_with(main_script->name))) { + const bool is_inner_by_path = (!main_script->path.is_empty()) && (class_type->fqcn.split("::")[0] == main_script->path); + const bool is_inner_by_name = (!main_script->name.is_empty()) && (class_type->fqcn.split("::")[0] == main_script->name); + if (is_inner_by_path || is_inner_by_name) { // Local class. List<StringName> names; while (class_type->outer) { @@ -126,8 +130,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D names.pop_back(); } result.kind = GDScriptDataType::GDSCRIPT; - result.script_type_ref = script; - result.script_type = result.script_type_ref.ptr(); + result.script_type = script.ptr(); result.native_type = script->get_instance_base_type(); } else { result.kind = GDScriptDataType::GDSCRIPT; @@ -317,10 +320,21 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } } + // Try globals. if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { - int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; - Variant global = GDScriptLanguage::get_singleton()->get_global_array()[idx]; - return codegen.add_constant(global); // TODO: Get type. + // If it's an autoload singleton, we postpone to load it at runtime. + // This is so one autoload doesn't try to load another before it's compiled. + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + if (autoloads.has(identifier) && autoloads[identifier].is_singleton) { + GDScriptCodeGenerator::Address global = codegen.add_temporary(_gdtype_from_datatype(in->get_datatype())); + int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; + gen->write_store_global(global, idx); + return global; + } else { + int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; + Variant global = GDScriptLanguage::get_singleton()->get_global_array()[idx]; + return codegen.add_constant(global); + } } // Try global classes. @@ -428,7 +442,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code break; case GDScriptParser::DictionaryNode::LUA_TABLE: // Lua-style: key is an identifier interpreted as StringName. - StringName key = static_cast<const GDScriptParser::IdentifierNode *>(dn->elements[i].key)->name; + StringName key = dn->elements[i].key->reduced_value.operator StringName(); element = codegen.add_constant(key); break; } @@ -474,6 +488,9 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_expression); GDScriptDataType type = _gdtype_from_datatype(call->get_datatype()); GDScriptCodeGenerator::Address result = codegen.add_temporary(type); + GDScriptCodeGenerator::Address nil = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::NIL); + + GDScriptCodeGenerator::Address return_addr = p_root ? nil : result; Vector<GDScriptCodeGenerator::Address> arguments; for (int i = 0; i < call->arguments.size(); i++) { @@ -524,52 +541,57 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code if (within_await) { gen->write_call_async(result, self, call->function_name, arguments); } else { - gen->write_call(result, self, call->function_name, arguments); + gen->write_call(return_addr, self, call->function_name, arguments); } } else { if (within_await) { gen->write_call_self_async(result, call->function_name, arguments); } else { - gen->write_call_self(result, call->function_name, arguments); + gen->write_call_self(return_addr, call->function_name, arguments); } } } else if (callee->type == GDScriptParser::Node::SUBSCRIPT) { const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(call->callee); if (subscript->is_attribute) { - GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base); - if (r_error) { - return GDScriptCodeGenerator::Address(); - } - if (within_await) { - gen->write_call_async(result, base, call->function_name, arguments); - } else if (base.type.has_type && base.type.kind != GDScriptDataType::BUILTIN) { - // Native method, use faster path. - StringName class_name; - if (base.type.kind == GDScriptDataType::NATIVE) { - class_name = base.type.native_type; - } else { - class_name = base.type.native_type == StringName() ? base.type.script_type->get_instance_base_type() : base.type.native_type; + // May be static built-in method call. + if (!call->is_super && subscript->base->type == GDScriptParser::Node::IDENTIFIER && GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(subscript->base)->name) < Variant::VARIANT_MAX) { + gen->write_call_builtin_type_static(result, GDScriptParser::get_builtin_type(static_cast<GDScriptParser::IdentifierNode *>(subscript->base)->name), subscript->attribute->name, arguments); + } else { + GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base); + if (r_error) { + return GDScriptCodeGenerator::Address(); } - if (ClassDB::class_exists(class_name) && ClassDB::has_method(class_name, call->function_name)) { - MethodBind *method = ClassDB::get_method(class_name, call->function_name); - if (_have_exact_arguments(method, arguments)) { - // Exact arguments, use ptrcall. - gen->write_call_ptrcall(result, base, method, arguments); + if (within_await) { + gen->write_call_async(result, base, call->function_name, arguments); + } else if (base.type.has_type && base.type.kind != GDScriptDataType::BUILTIN) { + // Native method, use faster path. + StringName class_name; + if (base.type.kind == GDScriptDataType::NATIVE) { + class_name = base.type.native_type; } else { - // Not exact arguments, but still can use method bind call. - gen->write_call_method_bind(result, base, method, arguments); + class_name = base.type.native_type == StringName() ? base.type.script_type->get_instance_base_type() : base.type.native_type; } + if (ClassDB::class_exists(class_name) && ClassDB::has_method(class_name, call->function_name)) { + MethodBind *method = ClassDB::get_method(class_name, call->function_name); + if (_have_exact_arguments(method, arguments)) { + // Exact arguments, use ptrcall. + gen->write_call_ptrcall(result, base, method, arguments); + } else { + // Not exact arguments, but still can use method bind call. + gen->write_call_method_bind(result, base, method, arguments); + } + } else { + gen->write_call(return_addr, base, call->function_name, arguments); + } + } else if (base.type.has_type && base.type.kind == GDScriptDataType::BUILTIN) { + gen->write_call_builtin_type(result, base, base.type.builtin_type, call->function_name, arguments); } else { - gen->write_call(result, base, call->function_name, arguments); + gen->write_call(return_addr, base, call->function_name, arguments); + } + if (base.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); } - } else if (base.type.has_type && base.type.kind == GDScriptDataType::BUILTIN) { - gen->write_call_builtin_type(result, base, base.type.builtin_type, call->function_name, arguments); - } else { - gen->write_call(result, base, call->function_name, arguments); - } - if (base.mode == GDScriptCodeGenerator::Address::TEMPORARY) { - gen->pop_temporary(); } } else { _set_error("Cannot call something that isn't a function.", call->callee); @@ -947,7 +969,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Perform operator if any. if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { - GDScriptCodeGenerator::Address value = codegen.add_temporary(); + GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype())); if (subscript->is_attribute) { gen->write_get_named(value, name, prev_base); } else { @@ -966,6 +988,9 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } else { gen->write_set(prev_base, key, assigned); } + if (key.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { gen->pop_temporary(); } @@ -973,8 +998,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code assigned = prev_base; // Set back the values into their bases. - for (List<ChainInfo>::Element *E = set_chain.front(); E; E = E->next()) { - const ChainInfo &info = E->get(); + for (const ChainInfo &info : set_chain) { if (!info.is_named) { gen->write_set(info.base, info.key, assigned); if (info.key.mode == GDScriptCodeGenerator::Address::TEMPORARY) { @@ -1000,25 +1024,32 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } } else if (assignment->assignee->type == GDScriptParser::Node::IDENTIFIER && _is_class_member_property(codegen, static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name)) { // Assignment to member property. - GDScriptCodeGenerator::Address assigned = _parse_expression(codegen, r_error, assignment->assigned_value); + GDScriptCodeGenerator::Address assigned_value = _parse_expression(codegen, r_error, assignment->assigned_value); if (r_error) { return GDScriptCodeGenerator::Address(); } - GDScriptCodeGenerator::Address assign_temp = assigned; + + GDScriptCodeGenerator::Address to_assign = assigned_value; + bool has_operation = assignment->operation != GDScriptParser::AssignmentNode::OP_NONE; StringName name = static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name; - if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { + if (has_operation) { + GDScriptCodeGenerator::Address op_result = codegen.add_temporary(); GDScriptCodeGenerator::Address member = codegen.add_temporary(); gen->write_get_member(member, name); - gen->write_binary_operator(assigned, assignment->variant_op, member, assigned); - gen->pop_temporary(); + gen->write_binary_operator(op_result, assignment->variant_op, member, assigned_value); + gen->pop_temporary(); // Pop member temp. + to_assign = op_result; } - gen->write_set_member(assigned, name); + gen->write_set_member(to_assign, name); - if (assign_temp.mode == GDScriptCodeGenerator::Address::TEMPORARY) { - gen->pop_temporary(); + if (to_assign.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); // Pop the assigned expression or the temp result if it has operation. + } + if (has_operation && assigned_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); // Pop the assigned expression if not done before. } } else { // Regular assignment. @@ -1052,19 +1083,25 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } } - GDScriptCodeGenerator::Address assigned = _parse_expression(codegen, r_error, assignment->assigned_value); - GDScriptCodeGenerator::Address op_result; + GDScriptCodeGenerator::Address assigned_value = _parse_expression(codegen, r_error, assignment->assigned_value); if (r_error) { return GDScriptCodeGenerator::Address(); } - if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { + GDScriptCodeGenerator::Address to_assign; + bool has_operation = assignment->operation != GDScriptParser::AssignmentNode::OP_NONE; + if (has_operation) { // Perform operation. - op_result = codegen.add_temporary(); - gen->write_binary_operator(op_result, assignment->variant_op, target, assigned); + GDScriptCodeGenerator::Address op_result = codegen.add_temporary(); + GDScriptCodeGenerator::Address og_value = _parse_expression(codegen, r_error, assignment->assignee); + gen->write_binary_operator(op_result, assignment->variant_op, og_value, assigned_value); + to_assign = op_result; + + if (og_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } } else { - op_result = assigned; - assigned = GDScriptCodeGenerator::Address(); + to_assign = assigned_value; } GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype()); @@ -1072,21 +1109,25 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code if (has_setter && !is_in_setter) { // Call setter. Vector<GDScriptCodeGenerator::Address> args; - args.push_back(op_result); + args.push_back(to_assign); gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), setter_function, args); } else { // Just assign. - gen->write_assign(target, op_result); + if (assignment->use_conversion_assign) { + gen->write_assign_with_conversion(target, to_assign); + } else { + gen->write_assign(target, to_assign); + } } - if (op_result.mode == GDScriptCodeGenerator::Address::TEMPORARY) { - gen->pop_temporary(); + if (to_assign.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); // Pop assigned value or temp operation result. } - if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { - gen->pop_temporary(); + if (has_operation && assigned_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); // Pop assigned value if not done before. } if (target.mode == GDScriptCodeGenerator::Address::TEMPORARY) { - gen->pop_temporary(); + gen->pop_temporary(); // Pop the target to assignment. } } return GDScriptCodeGenerator::Address(); // Assignment does not return a value. @@ -1784,7 +1825,11 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui if (error) { return error; } - gen->write_assign(local, src_address); + if (lv->use_conversion_assign) { + gen->write_assign_with_conversion(local, src_address); + } else { + gen->write_assign(local, src_address); + } if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } @@ -1843,7 +1888,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ StringName func_name; bool is_static = false; - MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + Multiplayer::RPCConfig rpc_config; GDScriptDataType return_type; return_type.has_type = true; return_type.kind = GDScriptDataType::BUILTIN; @@ -1856,7 +1901,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ func_name = "<anonymous lambda>"; } is_static = p_func->is_static; - rpc_mode = p_func->rpc_mode; + rpc_config = p_func->rpc_config; return_type = _gdtype_from_datatype(p_func->get_datatype(), p_script); } else { if (p_for_ready) { @@ -1867,7 +1912,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ } codegen.function_name = func_name; - codegen.generator->write_start(p_script, func_name, is_static, rpc_mode, return_type); + codegen.generator->write_start(p_script, func_name, is_static, rpc_config, return_type); int optional_parameters = 0; @@ -1922,7 +1967,11 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ return nullptr; } - codegen.generator->write_assign(dst_address, src_address); + if (field->use_conversion_assign) { + codegen.generator->write_assign_with_conversion(dst_address, src_address); + } else { + codegen.generator->write_assign(dst_address, src_address); + } if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } @@ -2043,77 +2092,18 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter) { Error error = OK; - CodeGen codegen; - codegen.generator = memnew(GDScriptByteCodeGenerator); - - codegen.class_node = p_class; - codegen.script = p_script; - - StringName func_name; - - if (p_is_setter) { - func_name = "@" + p_variable->identifier->name + "_setter"; - } else { - func_name = "@" + p_variable->identifier->name + "_getter"; - } - codegen.function_name = func_name; + GDScriptParser::FunctionNode *function; - GDScriptDataType return_type; if (p_is_setter) { - return_type.has_type = true; - return_type.kind = GDScriptDataType::BUILTIN; - return_type.builtin_type = Variant::NIL; + function = p_variable->setter; } else { - return_type = _gdtype_from_datatype(p_variable->get_datatype(), p_script); - } - - codegen.generator->write_start(p_script, func_name, false, p_variable->rpc_mode, return_type); - - if (p_is_setter) { - uint32_t par_addr = codegen.generator->add_parameter(p_variable->setter_parameter->name, false, _gdtype_from_datatype(p_variable->get_datatype())); - codegen.parameters[p_variable->setter_parameter->name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::FUNCTION_PARAMETER, par_addr, _gdtype_from_datatype(p_variable->get_datatype())); - } - - error = _parse_block(codegen, p_is_setter ? p_variable->setter : p_variable->getter); - if (error) { - memdelete(codegen.generator); - return error; - } - - GDScriptFunction *gd_function = codegen.generator->write_end(); - - p_script->member_functions[func_name] = gd_function; - -#ifdef DEBUG_ENABLED - if (EngineDebugger::is_active()) { - String signature; - //path - if (p_script->get_path() != String()) { - signature += p_script->get_path(); - } - //loc - signature += "::" + itos(p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line); - - //function and class - - if (p_class->identifier) { - signature += "::" + String(p_class->identifier->name) + "." + String(func_name); - } else { - signature += "::" + String(func_name); - } - - codegen.generator->set_signature(signature); + function = p_variable->getter; } -#endif - codegen.generator->set_initial_line(p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line); -#ifdef TOOLS_ENABLED - p_script->member_lines[func_name] = p_is_setter ? p_variable->setter->start_line : p_variable->getter->start_line; -#endif - memdelete(codegen.generator); + _parse_function(error, p_script, p_class, function); - return OK; + return error; } Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { @@ -2156,8 +2146,8 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->_base = nullptr; p_script->members.clear(); p_script->constants.clear(); - for (Map<StringName, GDScriptFunction *>::Element *E = p_script->member_functions.front(); E; E = E->next()) { - memdelete(E->get()); + for (const KeyValue<StringName, GDScriptFunction *> &E : p_script->member_functions) { + memdelete(E.value); } p_script->member_functions.clear(); p_script->member_indices.clear(); @@ -2168,6 +2158,13 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->tool = parser->is_tool(); p_script->name = p_class->identifier ? p_class->identifier->name : ""; + if (p_script->name != "") { + 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); + return ERR_ALREADY_EXISTS; + } + } + Ref<GDScriptNativeClass> native; GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type); @@ -2203,7 +2200,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar if (err) { return err; } - if (base.is_null() && !base->is_valid()) { + if (base.is_null() || !base->is_valid()) { return ERR_COMPILATION_FAILED; } } @@ -2248,7 +2245,6 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar } break; } - minfo.rpc_mode = variable->rpc_mode; minfo.data_type = _gdtype_from_datatype(variable->get_datatype(), p_script); PropertyInfo prop_info = minfo.data_type; @@ -2262,9 +2258,10 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar } prop_info.hint = export_info.hint; prop_info.hint_string = export_info.hint_string; - prop_info.usage = export_info.usage; + prop_info.usage = export_info.usage | PROPERTY_USAGE_SCRIPT_VARIABLE; + } else { + prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE; } - prop_info.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE; #ifdef TOOLS_ENABLED p_script->doc_variables[name] = variable->doc_description; #endif @@ -2319,28 +2316,6 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar const GDScriptParser::SignalNode *signal = member.signal; StringName name = signal->identifier->name; - GDScript *c = p_script; - - while (c) { - if (c->_signals.has(name)) { - _set_error("Signal '" + name + "' redefined (in current or parent class)", p_class); - return ERR_ALREADY_EXISTS; - } - - if (c->base.is_valid()) { - c = c->base.ptr(); - } else { - c = nullptr; - } - } - - if (native.is_valid()) { - if (ClassDB::has_signal(native->get_name(), name)) { - _set_error("Signal '" + name + "' redefined (original in native class '" + String(native->get_name()) + "')", p_class); - return ERR_ALREADY_EXISTS; - } - } - Vector<StringName> parameters_names; parameters_names.resize(signal->parameters.size()); for (int j = 0; j < signal->parameters.size(); j++) { @@ -2491,14 +2466,14 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa p_script->placeholders.erase(psi); //remove placeholder GDScriptInstance *instance = memnew(GDScriptInstance); - instance->base_ref = Object::cast_to<Reference>(E->get()); + instance->base_ref_counted = Object::cast_to<RefCounted>(E->get()); instance->members.resize(p_script->member_indices.size()); instance->script = Ref<GDScript>(p_script); instance->owner = E->get(); //needed for hot reloading - for (Map<StringName, GDScript::MemberInfo>::Element *F = p_script->member_indices.front(); F; F = F->next()) { - instance->member_indices_cache[F->key()] = F->get().index; + for (const KeyValue<StringName, GDScript::MemberInfo> &F : p_script->member_indices) { + instance->member_indices_cache[F.key] = F.value.index; } instance->owner->set_script_instance(instance); @@ -2566,7 +2541,7 @@ void GDScriptCompiler::_make_scripts(GDScript *p_script, const GDScriptParser::C if (orphan_subclass.is_valid()) { subclass = orphan_subclass; } else { - subclass.instance(); + subclass.instantiate(); } } diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index 789af57b4c..9287df2ea0 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -397,7 +397,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += DADDR(1 + argc); text += " = "; - text += "<unkown type>("; + text += "<unknown type>("; for (int i = 0; i < argc; i++) { if (i > 0) { text += ", "; @@ -542,6 +542,28 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr = 5 + argc; } break; + case OPCODE_CALL_BUILTIN_STATIC: { + Variant::Type type = (Variant::Type)_code_ptr[ip + 1 + instr_var_args]; + int argc = _code_ptr[ip + 3 + instr_var_args]; + + text += "call built-in method static "; + text += DADDR(1 + argc); + text += " = "; + text += Variant::get_type_name(type); + text += "."; + text += _global_names_ptr[_code_ptr[ip + 2 + instr_var_args]].operator String(); + text += "("; + + for (int i = 0; i < argc; i++) { + if (i > 0) { + text += ", "; + } + text += DADDR(1 + i); + } + text += ")"; + + incr += 5 + argc; + } break; case OPCODE_CALL_PTRCALL_NO_RETURN: { text += "call-ptrcall (no return) "; @@ -598,12 +620,12 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { DISASSEMBLE_PTRCALL(PLANE); DISASSEMBLE_PTRCALL(AABB); DISASSEMBLE_PTRCALL(BASIS); - DISASSEMBLE_PTRCALL(TRANSFORM); + DISASSEMBLE_PTRCALL(TRANSFORM3D); DISASSEMBLE_PTRCALL(COLOR); DISASSEMBLE_PTRCALL(STRING_NAME); DISASSEMBLE_PTRCALL(NODE_PATH); DISASSEMBLE_PTRCALL(RID); - DISASSEMBLE_PTRCALL(QUAT); + DISASSEMBLE_PTRCALL(QUATERNION); DISASSEMBLE_PTRCALL(OBJECT); DISASSEMBLE_PTRCALL(CALLABLE); DISASSEMBLE_PTRCALL(SIGNAL); @@ -666,7 +688,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { int argc = _code_ptr[ip + 1 + instr_var_args]; text += DADDR(1 + argc) + " = "; - text += "<unkown function>"; + text += "<unknown function>"; text += "("; for (int i = 0; i < argc; i++) { @@ -892,6 +914,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 5; } break; DISASSEMBLE_ITERATE_TYPES(DISASSEMBLE_ITERATE); + case OPCODE_STORE_GLOBAL: { + text += "store global "; + text += DADDR(1); + text += " = "; + text += String::num_int64(_code_ptr[ip + 2]); + + incr += 3; + } break; case OPCODE_STORE_NAMED_GLOBAL: { text += "store named global "; text += DADDR(1); @@ -935,7 +965,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { DISASSEMBLE_TYPE_ADJUST(VECTOR3I); DISASSEMBLE_TYPE_ADJUST(TRANSFORM2D); DISASSEMBLE_TYPE_ADJUST(PLANE); - DISASSEMBLE_TYPE_ADJUST(QUAT); + DISASSEMBLE_TYPE_ADJUST(QUATERNION); DISASSEMBLE_TYPE_ADJUST(AABB); DISASSEMBLE_TYPE_ADJUST(BASIS); DISASSEMBLE_TYPE_ADJUST(TRANSFORM); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 6ae825d2bd..71d2699c2e 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -32,7 +32,7 @@ #include "core/config/engine.h" #include "core/core_constants.h" -#include "core/os/file_access.h" +#include "core/io/file_access.h" #include "gdscript_analyzer.h" #include "gdscript_compiler.h" #include "gdscript_parser.h" @@ -104,7 +104,7 @@ Ref<Script> GDScriptLanguage::get_template(const String &p_class_name, const Str _template = _get_processed_template(_template, p_base_class_name); Ref<GDScript> script; - script.instance(); + script.instantiate(); script->set_source_code(_template); return script; @@ -131,7 +131,7 @@ static void get_function_names_recursively(const GDScriptParser::ClassNode *p_cl } } -bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, List<ScriptLanguage::Warning> *r_warnings, Set<int> *r_safe_lines) const { +bool GDScriptLanguage::validate(const String &p_script, const String &p_path, List<String> *r_functions, List<ScriptLanguage::ScriptError> *r_errors, List<ScriptLanguage::Warning> *r_warnings, Set<int> *r_safe_lines) const { GDScriptParser parser; GDScriptAnalyzer analyzer(&parser); @@ -141,8 +141,8 @@ bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int & } #ifdef DEBUG_ENABLED if (r_warnings) { - for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E; E = E->next()) { - const GDScriptWarning &warn = E->get(); + for (const GDScriptWarning &E : parser.get_warnings()) { + const GDScriptWarning &warn = E; ScriptLanguage::Warning w; w.start_line = warn.start_line; w.end_line = warn.end_line; @@ -156,10 +156,16 @@ bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int & } #endif if (err) { - GDScriptParser::ParserError parse_error = parser.get_errors().front()->get(); - r_line_error = parse_error.line; - r_col_error = parse_error.column; - r_test_error = parse_error.message; + if (r_errors) { + for (const GDScriptParser::ParserError &E : parser.get_errors()) { + const GDScriptParser::ParserError &pe = E; + ScriptLanguage::ScriptError e; + e.line = pe.line; + e.column = pe.column; + e.message = pe.message; + r_errors->push_back(e); + } + } return false; } else { const GDScriptParser::ClassNode *cl = parser.get_tree(); @@ -167,8 +173,8 @@ bool GDScriptLanguage::validate(const String &p_script, int &r_line_error, int & get_function_names_recursively(cl, "", funcs); - for (Map<int, String>::Element *E = funcs.front(); E; E = E->next()) { - r_functions->push_back(E->get() + ":" + itos(E->key())); + for (const KeyValue<int, String> &E : funcs) { + r_functions->push_back(E.value + ":" + itos(E.key)); } } @@ -313,9 +319,9 @@ void GDScriptLanguage::debug_get_stack_level_locals(int p_level, List<String> *p List<Pair<StringName, int>> locals; f->debug_get_stack_member_state(*_call_stack[l].line, &locals); - for (List<Pair<StringName, int>>::Element *E = locals.front(); E; E = E->next()) { - p_locals->push_back(E->get().first); - p_values->push_back(_call_stack[l].stack[E->get().second]); + for (const Pair<StringName, int> &E : locals) { + p_locals->push_back(E.first); + p_values->push_back(_call_stack[l].stack[E.second]); } } @@ -338,9 +344,9 @@ void GDScriptLanguage::debug_get_stack_level_members(int p_level, List<String> * const Map<StringName, GDScript::MemberInfo> &mi = script->debug_get_member_indices(); - for (const Map<StringName, GDScript::MemberInfo>::Element *E = mi.front(); E; E = E->next()) { - p_members->push_back(E->key()); - p_values->push_back(instance->debug_get_member_by_index(E->get().index)); + for (const KeyValue<StringName, GDScript::MemberInfo> &E : mi) { + p_members->push_back(E.key); + p_values->push_back(instance->debug_get_member_by_index(E.value.index)); } } @@ -364,14 +370,14 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> List<Pair<String, Variant>> cinfo; get_public_constants(&cinfo); - for (const Map<StringName, int>::Element *E = name_idx.front(); E; E = E->next()) { - if (ClassDB::class_exists(E->key()) || Engine::get_singleton()->has_singleton(E->key())) { + for (const KeyValue<StringName, int> &E : name_idx) { + if (ClassDB::class_exists(E.key) || Engine::get_singleton()->has_singleton(E.key)) { continue; } bool is_script_constant = false; for (List<Pair<String, Variant>>::Element *CE = cinfo.front(); CE; CE = CE->next()) { - if (CE->get().first == E->key()) { + if (CE->get().first == E.key) { is_script_constant = true; break; } @@ -380,7 +386,7 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> continue; } - const Variant &var = globals[E->value()]; + const Variant &var = globals[E.value]; if (Object *obj = var) { if (Object::cast_to<GDScriptNativeClass>(obj)) { continue; @@ -389,7 +395,7 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> bool skip = false; for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) { - if (E->key() == CoreConstants::get_global_constant_name(i)) { + if (E.key == CoreConstants::get_global_constant_name(i)) { skip = true; break; } @@ -398,7 +404,7 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> continue; } - p_globals->push_back(E->key()); + p_globals->push_back(E.key); p_values->push_back(var); } } @@ -415,8 +421,8 @@ void GDScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const List<StringName> functions; GDScriptUtilityFunctions::get_function_list(&functions); - for (const List<StringName>::Element *E = functions.front(); E; E = E->next()) { - p_functions->push_back(GDScriptUtilityFunctions::get_function_info(E->get())); + for (const StringName &E : functions) { + p_functions->push_back(GDScriptUtilityFunctions::get_function_info(E)); } // Not really "functions", but show in documentation. @@ -451,12 +457,12 @@ void GDScriptLanguage::get_public_constants(List<Pair<String, Variant>> *p_const Pair<String, Variant> infinity; infinity.first = "INF"; - infinity.second = Math_INF; + infinity.second = INFINITY; p_constants->push_back(infinity); Pair<String, Variant> nan; nan.first = "NAN"; - nan.second = Math_NAN; + nan.second = NAN; p_constants->push_back(nan); } @@ -542,7 +548,7 @@ static String _make_arguments_hint(const MethodInfo &p_info, int p_arg_idx, bool int def_args = p_info.arguments.size() - p_info.default_arguments.size(); int i = 0; - for (const List<PropertyInfo>::Element *E = p_info.arguments.front(); E; E = E->next()) { + for (const PropertyInfo &E : p_info.arguments) { if (i > 0) { arghint += ", "; } @@ -550,7 +556,7 @@ static String _make_arguments_hint(const MethodInfo &p_info, int p_arg_idx, bool if (i == p_arg_idx) { arghint += String::chr(0xFFFF); } - arghint += E->get().name + ": " + _get_visual_datatype(E->get(), true); + arghint += E.name + ": " + _get_visual_datatype(E, true); if (i - def_args >= 0) { arghint += String(" = ") + p_info.default_arguments[i - def_args].get_construct_string(); @@ -617,11 +623,11 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio } static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Map<String, ScriptCodeCompletionOption> &r_list) { - const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; + const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; for (int i = 0; i < p_dir->get_file_count(); i++) { ScriptCodeCompletionOption option(p_dir->get_file_path(i), ScriptCodeCompletionOption::KIND_FILE_PATH); - option.insert_text = quote_style + option.display + quote_style; + option.insert_text = option.display.quote(quote_style); r_list.insert(option.display, option); } @@ -631,24 +637,24 @@ static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Map<String } static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_annotation, int p_argument, const String p_quote_style, Map<String, ScriptCodeCompletionOption> &r_result) { - if (p_annotation->name == "@export_range" || p_annotation->name == "@export_exp_range") { + if (p_annotation->name == "@export_range") { if (p_argument == 3 || p_argument == 4) { // Slider hint. ScriptCodeCompletionOption slider1("or_greater", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); - slider1.insert_text = p_quote_style + slider1.display + p_quote_style; + slider1.insert_text = slider1.display.quote(p_quote_style); r_result.insert(slider1.display, slider1); ScriptCodeCompletionOption slider2("or_lesser", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); - slider2.insert_text = p_quote_style + slider2.display + p_quote_style; + slider2.insert_text = slider2.display.quote(p_quote_style); r_result.insert(slider2.display, slider2); } } else if (p_annotation->name == "@export_exp_easing") { if (p_argument == 0 || p_argument == 1) { // Easing hint. ScriptCodeCompletionOption hint1("attenuation", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); - hint1.insert_text = p_quote_style + hint1.display + p_quote_style; + hint1.insert_text = hint1.display.quote(p_quote_style); r_result.insert(hint1.display, hint1); ScriptCodeCompletionOption hint2("inout", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); - hint2.insert_text = p_quote_style + hint2.display + p_quote_style; + hint2.insert_text = hint2.display.quote(p_quote_style); r_result.insert(hint2.display, hint2); } } else if (p_annotation->name == "@export_node_path") { @@ -656,11 +662,11 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a r_result.insert(node.display, node); List<StringName> node_types; ClassDB::get_inheriters_from_class("Node", &node_types); - for (const List<StringName>::Element *E = node_types.front(); E != nullptr; E = E->next()) { - if (!ClassDB::is_class_exposed(E->get())) { + for (const StringName &E : node_types) { + if (!ClassDB::is_class_exposed(E)) { continue; } - ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CLASS); + ScriptCodeCompletionOption option(E, ScriptCodeCompletionOption::KIND_CLASS); r_result.insert(option.display, option); } } @@ -669,9 +675,9 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a static void _list_available_types(bool p_inherit_only, GDScriptParser::CompletionContext &p_context, Map<String, ScriptCodeCompletionOption> &r_result) { List<StringName> native_types; ClassDB::get_class_list(&native_types); - for (const List<StringName>::Element *E = native_types.front(); E != nullptr; E = E->next()) { - if (ClassDB::is_class_exposed(E->get()) && !Engine::get_singleton()->has_singleton(E->get())) { - ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CLASS); + for (const StringName &E : native_types) { + if (ClassDB::is_class_exposed(E) && !Engine::get_singleton()->has_singleton(E)) { + ScriptCodeCompletionOption option(E, ScriptCodeCompletionOption::KIND_CLASS); r_result.insert(option.display, option); } } @@ -681,8 +687,8 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio // Native enums from base class List<StringName> enums; ClassDB::get_enum_list(p_context.current_class->base_type.native_type, &enums); - for (const List<StringName>::Element *E = enums.front(); E != nullptr; E = E->next()) { - ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_ENUM); + for (const StringName &E : enums) { + ScriptCodeCompletionOption option(E, ScriptCodeCompletionOption::KIND_ENUM); r_result.insert(option.display, option); } } @@ -719,15 +725,15 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio // Global scripts List<StringName> global_classes; ScriptServer::get_global_class_list(&global_classes); - for (const List<StringName>::Element *E = global_classes.front(); E != nullptr; E = E->next()) { - ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CLASS); + for (const StringName &E : global_classes) { + ScriptCodeCompletionOption option(E, ScriptCodeCompletionOption::KIND_CLASS); r_result.insert(option.display, option); } // Autoload singletons - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (const Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E != nullptr; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->get(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.get(); if (!info.is_singleton || info.path.get_extension().to_lower() != "gd") { continue; } @@ -741,6 +747,7 @@ static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, ScriptCodeCompletionOption option; if (p_suite->locals[i].type == GDScriptParser::SuiteNode::Local::CONSTANT) { option = ScriptCodeCompletionOption(p_suite->locals[i].name, ScriptCodeCompletionOption::KIND_CONSTANT); + option.default_value = p_suite->locals[i].constant->initializer->reduced_value; } else { option = ScriptCodeCompletionOption(p_suite->locals[i].name, ScriptCodeCompletionOption::KIND_VARIABLE); } @@ -774,6 +781,9 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, if (p_only_functions) { continue; } + if (r_result.has(member.constant->identifier->name)) { + continue; + } option = ScriptCodeCompletionOption(member.constant->identifier->name, ScriptCodeCompletionOption::KIND_CONSTANT); if (member.constant->initializer) { option.default_value = member.constant->initializer->reduced_value; @@ -858,34 +868,34 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base if (!_static) { List<PropertyInfo> members; scr->get_script_property_list(&members); - for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); + for (const PropertyInfo &E : members) { + ScriptCodeCompletionOption option(E.name, ScriptCodeCompletionOption::KIND_MEMBER); r_result.insert(option.display, option); } } Map<StringName, Variant> constants; scr->get_constants(&constants); - for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); + for (const KeyValue<StringName, Variant> &E : constants) { + ScriptCodeCompletionOption option(E.key.operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); r_result.insert(option.display, option); } List<MethodInfo> signals; scr->get_script_signal_list(&signals); - for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_SIGNAL); + for (const MethodInfo &E : signals) { + ScriptCodeCompletionOption option(E.name, ScriptCodeCompletionOption::KIND_SIGNAL); r_result.insert(option.display, option); } } List<MethodInfo> methods; scr->get_script_method_list(&methods); - for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { - if (E->get().name.begins_with("@")) { + for (const MethodInfo &E : methods) { + if (E.name.begins_with("@")) { continue; } - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION); - if (E->get().arguments.size()) { + ScriptCodeCompletionOption option(E.name, ScriptCodeCompletionOption::KIND_FUNCTION); + if (E.arguments.size()) { option.insert_text += "("; } else { option.insert_text += "()"; @@ -905,7 +915,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base } } break; case GDScriptParser::DataType::NATIVE: { - StringName type = GDScriptParser::get_real_class_name(base_type.native_type); + StringName type = base_type.native_type; if (!ClassDB::class_exists(type)) { return; } @@ -913,22 +923,22 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base if (!p_only_functions) { List<String> constants; ClassDB::get_integer_constant_list(type, &constants); - for (List<String>::Element *E = constants.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CONSTANT); + for (const String &E : constants) { + ScriptCodeCompletionOption option(E, ScriptCodeCompletionOption::KIND_CONSTANT); r_result.insert(option.display, option); } if (!_static || Engine::get_singleton()->has_singleton(type)) { List<PropertyInfo> pinfo; ClassDB::get_property_list(type, &pinfo); - for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) { - if (E->get().usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) { + for (const PropertyInfo &E : pinfo) { + if (E.usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) { continue; } - if (E->get().name.find("/") != -1) { + if (E.name.find("/") != -1) { continue; } - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); + ScriptCodeCompletionOption option(E.name, ScriptCodeCompletionOption::KIND_MEMBER); r_result.insert(option.display, option); } } @@ -938,12 +948,12 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base List<MethodInfo> methods; bool is_autocompleting_getters = GLOBAL_GET("debug/gdscript/completion/autocomplete_setters_and_getters").booleanize(); ClassDB::get_method_list(type, &methods, false, !is_autocompleting_getters); - for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { - if (E->get().name.begins_with("_")) { + for (const MethodInfo &E : methods) { + if (E.name.begins_with("_")) { continue; } - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION); - if (E->get().arguments.size()) { + ScriptCodeCompletionOption option(E.name, ScriptCodeCompletionOption::KIND_FUNCTION); + if (E.arguments.size()) { option.insert_text += "("; } else { option.insert_text += "()"; @@ -970,9 +980,9 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base tmp.get_property_list(&members); } - for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { - if (String(E->get().name).find("/") == -1) { - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); + for (const PropertyInfo &E : members) { + if (String(E.name).find("/") == -1) { + ScriptCodeCompletionOption option(E.name, ScriptCodeCompletionOption::KIND_MEMBER); r_result.insert(option.display, option); } } @@ -980,9 +990,9 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base List<MethodInfo> methods; tmp.get_method_list(&methods); - for (const List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { - ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION); - if (E->get().arguments.size()) { + for (const MethodInfo &E : methods) { + ScriptCodeCompletionOption option(E.name, ScriptCodeCompletionOption::KIND_FUNCTION); + if (E.arguments.size()) { option.insert_text += "("; } else { option.insert_text += "()"; @@ -1012,9 +1022,9 @@ static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool List<StringName> functions; GDScriptUtilityFunctions::get_function_list(&functions); - for (const List<StringName>::Element *E = functions.front(); E; E = E->next()) { - MethodInfo function = GDScriptUtilityFunctions::get_function_info(E->get()); - ScriptCodeCompletionOption option(String(E->get()), ScriptCodeCompletionOption::KIND_FUNCTION); + for (const StringName &E : functions) { + MethodInfo function = GDScriptUtilityFunctions::get_function_info(E); + ScriptCodeCompletionOption option(String(E), ScriptCodeCompletionOption::KIND_FUNCTION); if (function.arguments.size() || (function.flags & METHOD_FLAG_VARARG)) { option.insert_text += "("; } else { @@ -1028,7 +1038,7 @@ static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool } static const char *_type_names[Variant::VARIANT_MAX] = { - "null", "bool", "int", "float", "String", "StringName", "Vector2", "Vector2i", "Rect2", "Rect2i", "Vector3", "Vector3i", "Transform2D", "Plane", "Quat", "AABB", "Basis", "Transform", + "null", "bool", "int", "float", "String", "StringName", "Vector2", "Vector2i", "Rect2", "Rect2i", "Vector3", "Vector3i", "Transform2D", "Plane", "Quaternion", "AABB", "Basis", "Transform3D", "Color", "NodePath", "RID", "Signal", "Callable", "Object", "Dictionary", "Array", "PackedByteArray", "PackedInt32Array", "PackedInt64Array", "PackedFloat32Array", "PackedFloat64Array", "PackedStringArray", "PackedVector2Array", "PackedVector3Array", "PackedColorArray" }; @@ -1079,22 +1089,31 @@ static void _find_identifiers(GDScriptParser::CompletionContext &p_context, bool kwa++; } - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (const Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E != nullptr; E = E->next()) { - if (!E->value().is_singleton) { + List<StringName> utility_func_names; + Variant::get_utility_function_list(&utility_func_names); + + for (List<StringName>::Element *E = utility_func_names.front(); E; E = E->next()) { + ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_FUNCTION); + option.insert_text += "("; + r_result.insert(option.display, option); + } + + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + if (!E.value().is_singleton) { continue; } - ScriptCodeCompletionOption option(E->key(), ScriptCodeCompletionOption::KIND_CONSTANT); + ScriptCodeCompletionOption option(E.key(), ScriptCodeCompletionOption::KIND_CONSTANT); r_result.insert(option.display, option); } // Native classes and global constants. - for (const Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) { + for (const KeyValue<StringName, int> &E : GDScriptLanguage::get_singleton()->get_global_map()) { ScriptCodeCompletionOption option; - if (ClassDB::class_exists(E->key()) || Engine::get_singleton()->has_singleton(E->key())) { - option = ScriptCodeCompletionOption(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); + if (ClassDB::class_exists(E.key) || Engine::get_singleton()->has_singleton(E.key)) { + option = ScriptCodeCompletionOption(E.key.operator String(), ScriptCodeCompletionOption::KIND_CLASS); } else { - option = ScriptCodeCompletionOption(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); + option = ScriptCodeCompletionOption(E.key.operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); } r_result.insert(option.display, option); } @@ -1319,10 +1338,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, native_type.kind = GDScriptParser::DataType::NATIVE; native_type.native_type = native_type.script_type->get_instance_base_type(); if (!ClassDB::class_exists(native_type.native_type)) { - native_type.native_type = String("_") + native_type.native_type; - if (!ClassDB::class_exists(native_type.native_type)) { - native_type.kind = GDScriptParser::DataType::UNRESOLVED; - } + native_type.kind = GDScriptParser::DataType::UNRESOLVED; } } } @@ -1355,12 +1371,12 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, r_type = _type_from_variant(GDScriptLanguage::get_singleton()->get_named_globals_map()[which]); found = true; } else { - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - String name = E->key(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + String name = E.key(); if (name == which) { - String script = E->value().path; + String script = E.value().path; if (!script.begins_with("res://")) { script = "res://" + script; @@ -1729,7 +1745,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } } - if (is_function_parameter && p_context.current_function && p_context.current_class) { + if (is_function_parameter && p_context.current_function && p_context.current_function->source_lambda == nullptr && p_context.current_class) { // Check if it's override of native function, then we can assume the type from the signature. GDScriptParser::DataType base_type = p_context.current_class->base_type; while (base_type.is_set()) { @@ -1758,12 +1774,11 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, base_type = GDScriptParser::DataType(); break; } - StringName real_native = GDScriptParser::get_real_class_name(base_type.native_type); MethodInfo info; - if (ClassDB::get_method_info(real_native, p_context.current_function->identifier->name, &info)) { - for (const List<PropertyInfo>::Element *E = info.arguments.front(); E; E = E->next()) { - if (E->get().name == p_identifier) { - r_type = _type_from_property(E->get()); + if (ClassDB::get_method_info(base_type.native_type, p_context.current_function->identifier->name, &info)) { + for (const PropertyInfo &E : info.arguments) { + if (E.name == p_identifier) { + r_type = _type_from_property(E); return true; } } @@ -1829,13 +1844,12 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } // Check ClassDB. - StringName class_name = GDScriptParser::get_real_class_name(p_identifier); - if (ClassDB::class_exists(class_name) && ClassDB::is_class_exposed(class_name)) { + if (ClassDB::class_exists(p_identifier) && ClassDB::is_class_exposed(p_identifier)) { r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.kind = GDScriptParser::DataType::NATIVE; r_type.type.native_type = p_identifier; r_type.type.is_constant = true; - r_type.type.is_meta_type = !Engine::get_singleton()->has_singleton(class_name); + r_type.type.is_meta_type = !Engine::get_singleton()->has_singleton(p_identifier); r_type.value = Variant(); } @@ -1925,8 +1939,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & if (!is_static) { List<PropertyInfo> members; scr->get_script_property_list(&members); - for (const List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { - const PropertyInfo &prop = E->get(); + for (const PropertyInfo &prop : members) { if (prop.name == p_identifier) { r_type = _type_from_property(prop); return true; @@ -1945,7 +1958,7 @@ static bool _guess_identifier_type_from_base(GDScriptParser::CompletionContext & } } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type); + StringName class_name = base_type.native_type; if (!ClassDB::class_exists(class_name)) { return false; } @@ -2089,8 +2102,7 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex if (scr.is_valid()) { List<MethodInfo> methods; scr->get_script_method_list(&methods); - for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { - MethodInfo &mi = E->get(); + for (const MethodInfo &mi : methods) { if (mi.name == p_method) { r_type = _type_from_property(mi.return_val); return true; @@ -2108,11 +2120,10 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex } } break; case GDScriptParser::DataType::NATIVE: { - StringName native = GDScriptParser::get_real_class_name(base_type.native_type); - if (!ClassDB::class_exists(native)) { + if (!ClassDB::class_exists(base_type.native_type)) { return false; } - MethodBind *mb = ClassDB::get_method(native, p_method); + MethodBind *mb = ClassDB::get_method(base_type.native_type, p_method); if (mb) { r_type = _type_from_property(mb->get_return_info()); return true; @@ -2130,8 +2141,7 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex List<MethodInfo> methods; tmp.get_method_list(&methods); - for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { - MethodInfo &mi = E->get(); + for (const MethodInfo &mi : methods) { if (mi.name == p_method) { r_type = _type_from_property(mi.return_val); return true; @@ -2176,8 +2186,8 @@ static void _find_enumeration_candidates(GDScriptParser::CompletionContext &p_co List<StringName> enum_constants; ClassDB::get_enum_constants(class_name, enum_name, &enum_constants); - for (List<StringName>::Element *E = enum_constants.front(); E; E = E->next()) { - String candidate = class_name + "." + E->get(); + for (const StringName &E : enum_constants) { + String candidate = class_name + "." + E; ScriptCodeCompletionOption option(candidate, ScriptCodeCompletionOption::KIND_ENUM); r_result.insert(option.display, option); } @@ -2188,7 +2198,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c Variant base = p_base.value; GDScriptParser::DataType base_type = p_base.type; - const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; + const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; while (base_type.is_set() && !base_type.is_variant()) { switch (base_type.kind) { @@ -2205,7 +2215,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c base_type = base_type.class_type->base_type; } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type); + StringName class_name = base_type.native_type; if (!ClassDB::class_exists(class_name)) { base_type.kind = GDScriptParser::DataType::UNRESOLVED; break; @@ -2221,8 +2231,11 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c if (obj) { List<String> options; obj->get_argument_options(p_method, p_argidx, &options); - for (List<String>::Element *F = options.front(); F; F = F->next()) { - ScriptCodeCompletionOption option(F->get(), ScriptCodeCompletionOption::KIND_FUNCTION); + for (String &opt : options) { + if (opt.is_quoted()) { + opt = opt.unquote().quote(quote_style); // Handle user preference. + } + ScriptCodeCompletionOption option(opt, ScriptCodeCompletionOption::KIND_FUNCTION); r_result.insert(option.display, option); } } @@ -2243,14 +2256,14 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - String s = E->get().name; + for (const PropertyInfo &E : props) { + String s = E.name; if (!s.begins_with("autoload/")) { continue; } String name = s.get_slice("/", 1); ScriptCodeCompletionOption option("/root/" + name, ScriptCodeCompletionOption::KIND_NODE_PATH); - option.insert_text = quote_style + option.display + quote_style; + option.insert_text = option.display.quote(quote_style); r_result.insert(option.display, option); } } @@ -2259,14 +2272,14 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c // Get input actions List<PropertyInfo> props; ProjectSettings::get_singleton()->get_property_list(&props); - for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { - String s = E->get().name; + for (const PropertyInfo &E : props) { + String s = E.name; if (!s.begins_with("input/")) { continue; } String name = s.get_slice("/", 1); ScriptCodeCompletionOption option(name, ScriptCodeCompletionOption::KIND_CONSTANT); - option.insert_text = quote_style + option.display + quote_style; + option.insert_text = option.display.quote(quote_style); r_result.insert(option.display, option); } } @@ -2284,9 +2297,9 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c List<MethodInfo> methods; base.get_method_list(&methods); - for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { - if (E->get().name == p_method) { - r_arghint = _make_arguments_hint(E->get(), p_argidx); + for (const MethodInfo &E : methods) { + if (E.name == p_method) { + r_arghint = _make_arguments_hint(E, p_argidx); return; } } @@ -2301,8 +2314,6 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, const GDScriptParser::Node *p_call, int p_argidx, Map<String, ScriptCodeCompletionOption> &r_result, bool &r_forced, String &r_arghint) { - const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; - if (p_call->type == GDScriptParser::Node::PRELOAD) { if (p_argidx == 0 && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) { _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), r_result); @@ -2323,7 +2334,11 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c GDScriptCompletionIdentifier connect_base; - if (GDScriptUtilityFunctions::function_exists(call->function_name)) { + if (Variant::has_utility_function(call->function_name)) { + MethodInfo info = Variant::get_utility_function_info(call->function_name); + r_arghint = _make_arguments_hint(info, p_argidx); + return; + } else if (GDScriptUtilityFunctions::function_exists(call->function_name)) { MethodInfo info = GDScriptUtilityFunctions::get_function_info(call->function_name); r_arghint = _make_arguments_hint(info, p_argidx); return; @@ -2333,14 +2348,14 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c Variant::get_constructor_list(GDScriptParser::get_builtin_type(call->function_name), &constructors); int i = 0; - for (List<MethodInfo>::Element *E = constructors.front(); E; E = E->next()) { - if (p_argidx >= E->get().arguments.size()) { + for (const MethodInfo &E : constructors) { + if (p_argidx >= E.arguments.size()) { continue; } if (i > 0) { r_arghint += "\n"; } - r_arghint += _make_arguments_hint(E->get(), p_argidx); + r_arghint += _make_arguments_hint(E, p_argidx); i++; } return; @@ -2377,8 +2392,8 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c r_forced = r_result.size() > 0; } -Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) { - const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; +::Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) { + const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; GDScriptParser parser; GDScriptAnalyzer analyzer(&parser); @@ -2399,9 +2414,9 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path case GDScriptParser::COMPLETION_ANNOTATION: { List<MethodInfo> annotations; parser.get_annotation_list(&annotations); - for (const List<MethodInfo>::Element *E = annotations.front(); E != nullptr; E = E->next()) { - ScriptCodeCompletionOption option(E->get().name.substr(1), ScriptCodeCompletionOption::KIND_PLAIN_TEXT); - if (E->get().arguments.size() > 0) { + for (const MethodInfo &E : annotations) { + ScriptCodeCompletionOption option(E.name.substr(1), ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + if (E.arguments.size() > 0) { option.insert_text += "("; } options.insert(option.display, option); @@ -2419,10 +2434,10 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path case GDScriptParser::COMPLETION_BUILT_IN_TYPE_CONSTANT: { List<StringName> constants; Variant::get_constants_for_type(completion_context.builtin_type, &constants); - for (const List<StringName>::Element *E = constants.front(); E != nullptr; E = E->next()) { - ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CONSTANT); + for (const StringName &E : constants) { + ScriptCodeCompletionOption option(E, ScriptCodeCompletionOption::KIND_CONSTANT); bool valid = false; - Variant default_value = Variant::get_constant_value(completion_context.builtin_type, E->get(), &valid); + Variant default_value = Variant::get_constant_value(completion_context.builtin_type, E, &valid); if (valid) { option.default_value = default_value; } @@ -2590,7 +2605,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path break; } - StringName class_name = GDScriptParser::get_real_class_name(native_type.native_type); + StringName class_name = native_type.native_type; if (!ClassDB::class_exists(class_name)) { break; } @@ -2599,8 +2614,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path List<MethodInfo> virtual_methods; ClassDB::get_virtual_methods(class_name, &virtual_methods); - for (List<MethodInfo>::Element *E = virtual_methods.front(); E; E = E->next()) { - MethodInfo &mi = E->get(); + for (const MethodInfo &mi : virtual_methods) { String method_hint = mi.name; if (method_hint.find(":") != -1) { method_hint = method_hint.get_slice(":", 0); @@ -2645,31 +2659,34 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path } } break; case GDScriptParser::COMPLETION_GET_NODE: { + // Handles the `$Node/Path` or `$"Some NodePath"` syntax specifically. if (p_owner) { List<String> opts; p_owner->get_argument_options("get_node", 0, &opts); - for (List<String>::Element *E = opts.front(); E; E = E->next()) { - String opt = E->get().strip_edges(); + for (const String &E : opts) { + r_forced = true; + String opt = E.strip_edges(); if (opt.is_quoted()) { - r_forced = true; - String idopt = opt.unquote(); - if (idopt.replace("/", "_").is_valid_identifier()) { - ScriptCodeCompletionOption option(idopt, ScriptCodeCompletionOption::KIND_NODE_PATH); - options.insert(option.display, option); - } else { - ScriptCodeCompletionOption option(opt, ScriptCodeCompletionOption::KIND_NODE_PATH); - options.insert(option.display, option); - } + // Remove quotes so that we can handle user preferred quote style, + // or handle NodePaths which are valid identifiers and don't need quotes. + opt = opt.unquote(); + } + // The path needs quotes if it's not a valid identifier (with an exception + // for "/" as path separator, which also doesn't require quotes). + if (!opt.replace("/", "_").is_valid_identifier()) { + opt = opt.quote(quote_style); // Handle user preference. } + ScriptCodeCompletionOption option(opt, ScriptCodeCompletionOption::KIND_NODE_PATH); + options.insert(option.display, option); } // Get autoloads. - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - String name = E->key(); - ScriptCodeCompletionOption option(quote_style + "/root/" + name + quote_style, ScriptCodeCompletionOption::KIND_NODE_PATH); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + String path = "/root/" + E.key(); + ScriptCodeCompletionOption option(path.quote(quote_style), ScriptCodeCompletionOption::KIND_NODE_PATH); options.insert(option.display, option); } } @@ -2682,8 +2699,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path } break; } - for (Map<String, ScriptCodeCompletionOption>::Element *E = options.front(); E; E = E->next()) { - r_options->push_back(E->get()); + for (const KeyValue<String, ScriptCodeCompletionOption> &E : options) { + r_options->push_back(E.value); } return OK; @@ -2702,10 +2719,10 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path String GDScriptLanguage::_get_indentation() const { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) { - bool use_space_indentation = EDITOR_DEF("text_editor/indent/type", false); + bool use_space_indentation = EDITOR_DEF("text_editor/behavior/indent/type", false); if (use_space_indentation) { - int indent_size = EDITOR_DEF("text_editor/indent/size", 4); + int indent_size = EDITOR_DEF("text_editor/behavior/indent/size", 4); String space_indent = ""; for (int i = 0; i < indent_size; i++) { @@ -2792,6 +2809,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co if (base_type.class_type->has_member(p_symbol)) { r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION; r_result.location = base_type.class_type->get_member(p_symbol).get_line(); + r_result.class_path = base_type.script_path; return OK; } base_type = base_type.class_type->base_type; @@ -2819,7 +2837,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } } break; case GDScriptParser::DataType::NATIVE: { - StringName class_name = GDScriptParser::get_real_class_name(base_type.native_type); + StringName class_name = base_type.native_type; if (!ClassDB::class_exists(class_name)) { base_type.kind = GDScriptParser::DataType::UNRESOLVED; break; @@ -2834,8 +2852,8 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co List<MethodInfo> virtual_methods; ClassDB::get_virtual_methods(class_name, &virtual_methods, true); - for (List<MethodInfo>::Element *E = virtual_methods.front(); E; E = E->next()) { - if (E->get().name == p_symbol) { + for (const MethodInfo &E : virtual_methods) { + if (E.name == p_symbol) { r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_METHOD; r_result.class_name = base_type.native_type; r_result.class_member = p_symbol; @@ -2853,8 +2871,8 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co List<String> constants; ClassDB::get_integer_constant_list(class_name, &constants, true); - for (List<String>::Element *E = constants.front(); E; E = E->next()) { - if (E->get() == p_symbol) { + for (const String &E : constants) { + if (E == p_symbol) { r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT; r_result.class_name = base_type.native_type; r_result.class_member = p_symbol; @@ -2871,11 +2889,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co StringName parent = ClassDB::get_parent_class(class_name); if (parent != StringName()) { - if (String(parent).begins_with("_")) { - base_type.native_type = String(parent).right(1); - } else { - base_type.native_type = parent; - } + base_type.native_type = parent; } else { base_type.kind = GDScriptParser::DataType::UNRESOLVED; } @@ -2893,7 +2907,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co Variant v; REF v_ref; if (base_type.builtin_type == Variant::OBJECT) { - v_ref.instance(); + v_ref.instantiate(); v = v_ref; } else { Callable::CallError err; @@ -2928,19 +2942,12 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co return ERR_CANT_RESOLVE; } -Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) { - //before parsing, try the usual stuff +::Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result) { + // Before parsing, try the usual stuff if (ClassDB::class_exists(p_symbol)) { r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS; r_result.class_name = p_symbol; return OK; - } else { - String under_prefix = "_" + p_symbol; - if (ClassDB::class_exists(under_prefix)) { - r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS; - r_result.class_name = p_symbol; - return OK; - } } for (int i = 0; i < Variant::VARIANT_MAX; i++) { @@ -3031,9 +3038,9 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol if (!is_function) { // Guess in autoloads as singletons. if (ProjectSettings::get_singleton()->has_autoload(p_symbol)) { - const ProjectSettings::AutoloadInfo &singleton = ProjectSettings::get_singleton()->get_autoload(p_symbol); - if (singleton.is_singleton) { - String script = singleton.path; + const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(p_symbol); + if (autoload.is_singleton) { + String script = autoload.path; if (!script.ends_with(".gd")) { // Not a script, try find the script anyway, // may have some success. @@ -3066,7 +3073,7 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol // proxy class remove the underscore. if (r_result.class_name.begins_with("_")) { - r_result.class_name = r_result.class_name.right(1); + r_result.class_name = r_result.class_name.substr(1); } return OK; } @@ -3093,6 +3100,15 @@ Error GDScriptLanguage::lookup_code(const String &p_code, const String &p_symbol r_result.class_member = p_symbol; return OK; } + } else { + List<StringName> utility_functions; + Variant::get_utility_function_list(&utility_functions); + if (utility_functions.find(p_symbol) != nullptr) { + r_result.type = ScriptLanguage::LookupResult::RESULT_CLASS_TBD_GLOBALSCOPE; + r_result.class_name = "@GlobalScope"; + r_result.class_member = p_symbol; + return OK; + } } } } break; diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 78399114a5..a3f0c7dfef 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -94,8 +94,7 @@ struct _GDFKCS { void GDScriptFunction::debug_get_stack_member_state(int p_line, List<Pair<StringName, int>> *r_stackvars) const { int oc = 0; Map<StringName, _GDFKC> sdmap; - for (const List<StackDebug>::Element *E = stack_debug.front(); E; E = E->next()) { - const StackDebug &sd = E->get(); + for (const StackDebug &sd : stack_debug) { if (sd.line > p_line) { break; } @@ -121,20 +120,20 @@ void GDScriptFunction::debug_get_stack_member_state(int p_line, List<Pair<String } List<_GDFKCS> stackpositions; - for (Map<StringName, _GDFKC>::Element *E = sdmap.front(); E; E = E->next()) { + for (const KeyValue<StringName, _GDFKC> &E : sdmap) { _GDFKCS spp; - spp.id = E->key(); - spp.order = E->get().order; - spp.pos = E->get().pos.back()->get(); + spp.id = E.key; + spp.order = E.value.order; + spp.pos = E.value.pos.back()->get(); stackpositions.push_back(spp); } stackpositions.sort(); - for (List<_GDFKCS>::Element *E = stackpositions.front(); E; E = E->next()) { + for (_GDFKCS &E : stackpositions) { Pair<StringName, int> p; - p.first = E->get().id; - p.second = E->get().pos; + p.first = E.id; + p.second = E.pos; r_stackvars->push_back(p); } } @@ -262,9 +261,9 @@ Variant GDScriptFunctionState::resume(const Variant &p_arg) { if (completed) { if (first_state.is_valid()) { - first_state->emit_signal("completed", ret); + first_state->emit_signal(SNAME("completed"), ret); } else { - emit_signal("completed", ret); + emit_signal(SNAME("completed"), ret); } #ifdef DEBUG_ENABLED diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 70b62ced6d..9d076a8e4c 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -31,7 +31,7 @@ #ifndef GDSCRIPT_FUNCTION_H #define GDSCRIPT_FUNCTION_H -#include "core/object/reference.h" +#include "core/object/ref_counted.h" #include "core/object/script_language.h" #include "core/os/thread.h" #include "core/string/string_name.h" @@ -111,11 +111,7 @@ public: } if (!ClassDB::is_parent_class(obj->get_class_name(), native_type)) { - // Try with underscore prefix - StringName underscore_native_type = "_" + native_type; - if (!ClassDB::is_parent_class(obj->get_class_name(), underscore_native_type)) { - return false; - } + return false; } return true; } break; @@ -263,6 +259,7 @@ public: OPCODE_CALL_SELF_BASE, OPCODE_CALL_METHOD_BIND, OPCODE_CALL_METHOD_BIND_RET, + OPCODE_CALL_BUILTIN_STATIC, // ptrcall have one instruction per return type. OPCODE_CALL_PTRCALL_NO_RETURN, OPCODE_CALL_PTRCALL_BOOL, @@ -277,10 +274,10 @@ public: OPCODE_CALL_PTRCALL_VECTOR3I, OPCODE_CALL_PTRCALL_TRANSFORM2D, OPCODE_CALL_PTRCALL_PLANE, - OPCODE_CALL_PTRCALL_QUAT, + OPCODE_CALL_PTRCALL_QUATERNION, OPCODE_CALL_PTRCALL_AABB, OPCODE_CALL_PTRCALL_BASIS, - OPCODE_CALL_PTRCALL_TRANSFORM, + OPCODE_CALL_PTRCALL_TRANSFORM3D, OPCODE_CALL_PTRCALL_COLOR, OPCODE_CALL_PTRCALL_STRING_NAME, OPCODE_CALL_PTRCALL_NODE_PATH, @@ -351,6 +348,7 @@ public: 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, @@ -364,7 +362,7 @@ public: OPCODE_TYPE_ADJUST_VECTOR3I, OPCODE_TYPE_ADJUST_TRANSFORM2D, OPCODE_TYPE_ADJUST_PLANE, - OPCODE_TYPE_ADJUST_QUAT, + OPCODE_TYPE_ADJUST_QUATERNION, OPCODE_TYPE_ADJUST_AABB, OPCODE_TYPE_ADJUST_BASIS, OPCODE_TYPE_ADJUST_TRANSFORM, @@ -471,7 +469,7 @@ private: int _initial_line = 0; bool _static = false; - MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + Multiplayer::RPCConfig rpc_config; GDScript *_script = nullptr; @@ -496,6 +494,8 @@ private: Vector<GDScriptDataType> argument_types; GDScriptDataType return_type; + Map<int, Variant::Type> temporary_slots; + #ifdef TOOLS_ENABLED Vector<StringName> arg_names; Vector<Variant> default_arg_values; @@ -589,13 +589,13 @@ public: void disassemble(const Vector<String> &p_code_lines) const; #endif - _FORCE_INLINE_ MultiplayerAPI::RPCMode get_rpc_mode() const { return rpc_mode; } + _FORCE_INLINE_ Multiplayer::RPCConfig get_rpc_config() const { return rpc_config; } GDScriptFunction(); ~GDScriptFunction(); }; -class GDScriptFunctionState : public Reference { - GDCLASS(GDScriptFunctionState, Reference); +class GDScriptFunctionState : public RefCounted { + GDCLASS(GDScriptFunctionState, RefCounted); friend class GDScriptFunction; GDScriptFunction *function = nullptr; GDScriptFunction::CallState state; diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h index 357c845250..336778d549 100644 --- a/modules/gdscript/gdscript_lambda_callable.h +++ b/modules/gdscript/gdscript_lambda_callable.h @@ -31,7 +31,7 @@ #ifndef GDSCRIPT_LAMBDA_CALLABLE #define GDSCRIPT_LAMBDA_CALLABLE -#include "core/object/reference.h" +#include "core/object/ref_counted.h" #include "core/templates/vector.h" #include "core/variant/callable.h" #include "core/variant/variant.h" diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index f9027c3a87..41b2d2191c 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -31,9 +31,9 @@ #include "gdscript_parser.h" #include "core/config/project_settings.h" +#include "core/io/file_access.h" #include "core/io/resource_loader.h" #include "core/math/math_defs.h" -#include "core/os/file_access.h" #include "gdscript.h" #ifdef DEBUG_ENABLED @@ -61,9 +61,9 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { builtin_types["Vector3i"] = Variant::VECTOR3I; builtin_types["AABB"] = Variant::AABB; builtin_types["Plane"] = Variant::PLANE; - builtin_types["Quat"] = Variant::QUAT; + builtin_types["Quaternion"] = Variant::QUATERNION; builtin_types["Basis"] = Variant::BASIS; - builtin_types["Transform"] = Variant::TRANSFORM; + builtin_types["Transform3D"] = Variant::TRANSFORM3D; builtin_types["Color"] = Variant::COLOR; builtin_types["RID"] = Variant::RID; builtin_types["Object"] = Variant::OBJECT; @@ -94,50 +94,15 @@ Variant::Type GDScriptParser::get_builtin_type(const StringName &p_type) { return Variant::VARIANT_MAX; } -// TODO: Move this to a central location (maybe core?). -static HashMap<StringName, StringName> underscore_map; -static const char *underscore_classes[] = { - "ClassDB", - "Directory", - "Engine", - "File", - "Geometry", - "GodotSharp", - "JSON", - "Marshalls", - "Mutex", - "OS", - "ResourceLoader", - "ResourceSaver", - "Semaphore", - "Thread", - "VisualScriptEditor", - nullptr, -}; -StringName GDScriptParser::get_real_class_name(const StringName &p_source) { - if (underscore_map.is_empty()) { - const char **class_name = underscore_classes; - while (*class_name != nullptr) { - underscore_map[*class_name] = String("_") + *class_name; - class_name++; - } - } - if (underscore_map.has(p_source)) { - return underscore_map[p_source]; - } - return p_source; -} - void GDScriptParser::cleanup() { builtin_types.clear(); - underscore_map.clear(); } void GDScriptParser::get_annotation_list(List<MethodInfo> *r_annotations) const { List<StringName> keys; valid_annotations.get_key_list(&keys); - for (const List<StringName>::Element *E = keys.front(); E != nullptr; E = E->next()) { - r_annotations->push_back(valid_annotations[E->get()].info); + for (const StringName &E : keys) { + r_annotations->push_back(valid_annotations[E].info); } } @@ -157,7 +122,6 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_multiline"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_MULTILINE_TEXT, Variant::STRING>); register_annotation(MethodInfo("@export_placeholder"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>); register_annotation(MethodInfo("@export_range", { Variant::FLOAT, "min" }, { Variant::FLOAT, "max" }, { Variant::FLOAT, "step" }, { Variant::STRING, "slider1" }, { Variant::STRING, "slider2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, 3); - register_annotation(MethodInfo("@export_exp_range", { Variant::FLOAT, "min" }, { Variant::FLOAT, "max" }, { Variant::FLOAT, "step" }, { Variant::STRING, "slider1" }, { Variant::STRING, "slider2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_RANGE, Variant::FLOAT>, 3); register_annotation(MethodInfo("@export_exp_easing", { Variant::STRING, "hint1" }, { Variant::STRING, "hint2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, 2); register_annotation(MethodInfo("@export_color_no_alpha"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_COLOR_NO_ALPHA, Variant::COLOR>); register_annotation(MethodInfo("@export_node_path", { Variant::STRING, "type" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NODE_PATH_VALID_TYPES, Variant::NODE_PATH>, 1, true); @@ -169,12 +133,7 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>); // Networking. - register_annotation(MethodInfo("@remote"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_REMOTE>); - register_annotation(MethodInfo("@master"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_MASTER>); - register_annotation(MethodInfo("@puppet"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_PUPPET>); - register_annotation(MethodInfo("@remotesync"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_REMOTESYNC>); - register_annotation(MethodInfo("@mastersync"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_MASTERSYNC>); - register_annotation(MethodInfo("@puppetsync"), AnnotationInfo::VARIABLE | AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<MultiplayerAPI::RPC_MODE_PUPPETSYNC>); + register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<Multiplayer::RPC_MODE_AUTHORITY>, 4, true); // TODO: Warning annotations. } @@ -254,7 +213,7 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_ warning.rightmost_column = p_source->rightmost_column; List<GDScriptWarning>::Element *before = nullptr; - for (List<GDScriptWarning>::Element *E = warnings.front(); E != nullptr; E = E->next()) { + for (List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) { if (E->get().start_line > warning.start_line) { break; } @@ -343,7 +302,7 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ int tab_size = 4; #ifdef TOOLS_ENABLED if (EditorSettings::get_singleton()) { - tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size"); + tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size"); } #endif // TOOLS_ENABLED @@ -378,12 +337,29 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ tokenizer.set_cursor_position(cursor_line, cursor_column); script_path = p_script_path; current = tokenizer.scan(); - // Avoid error as the first token. - while (current.type == GDScriptTokenizer::Token::ERROR) { - push_error(current.literal); + // Avoid error or newline as the first token. + // The latter can mess with the parser when opening files filled exclusively with comments and newlines. + while (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::NEWLINE) { + if (current.type == GDScriptTokenizer::Token::ERROR) { + push_error(current.literal); + } current = tokenizer.scan(); } +#ifdef DEBUG_ENABLED + // Warn about parsing an empty script file: + if (current.type == GDScriptTokenizer::Token::TK_EOF) { + // Create a dummy Node for the warning, pointing to the very beginning of the file + Node *nd = alloc_node<PassNode>(); + nd->start_line = 1; + nd->start_column = 0; + nd->end_line = 1; + nd->leftmost_column = 0; + nd->rightmost_column = 0; + push_warning(nd, GDScriptWarning::EMPTY_FILE); + } +#endif + push_multiline(false); // Keep one for the whole parsing. parse_program(); pop_multiline(); @@ -603,12 +579,12 @@ void GDScriptParser::parse_program() { } } - parse_class_body(); + parse_class_body(true); #ifdef TOOLS_ENABLED - for (Map<int, GDScriptTokenizer::CommentData>::Element *E = tokenizer.get_comments().front(); E; E = E->next()) { - if (E->get().new_line && E->get().comment.begins_with("##")) { - class_doc_line = MIN(class_doc_line, E->key()); + 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); } } if (has_comment(class_doc_line)) { @@ -639,9 +615,10 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() { } consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after class declaration.)"); - consume(GDScriptTokenizer::Token::NEWLINE, R"(Expected newline after class declaration.)"); - if (!consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block after class declaration.)")) { + bool multiline = match(GDScriptTokenizer::Token::NEWLINE); + + if (multiline && !consume(GDScriptTokenizer::Token::INDENT, R"(Expected indented block after class declaration.)")) { current_class = previous_class; return n_class; } @@ -654,9 +631,11 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() { end_statement("superclass"); } - parse_class_body(); + parse_class_body(multiline); - consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)"); + if (multiline) { + consume(GDScriptTokenizer::Token::DEDENT, R"(Missing unindent at the end of the class body.)"); + } current_class = previous_class; return n_class; @@ -761,17 +740,33 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)() #endif // TOOLS_ENABLED if (member->identifier != nullptr) { - // Enums may be unnamed. - // TODO: Consider names in outer scope too, for constants and classes (and static functions?) - if (current_class->members_indices.has(member->identifier->name)) { - push_error(vformat(R"(%s "%s" has the same name as a previously declared %s.)", p_member_kind.capitalize(), member->identifier->name, current_class->get_member(member->identifier->name).get_type_name()), member->identifier); + if (!((String)member->identifier->name).is_empty()) { // Enums may be unnamed. + +#ifdef DEBUG_ENABLED + List<MethodInfo> gdscript_funcs; + GDScriptLanguage::get_singleton()->get_public_functions(&gdscript_funcs); + for (MethodInfo &info : gdscript_funcs) { + if (info.name == member->identifier->name) { + push_warning(member->identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_member_kind, member->identifier->name, "built-in function"); + } + } + if (Variant::has_utility_function(member->identifier->name)) { + push_warning(member->identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, p_member_kind, member->identifier->name, "built-in function"); + } +#endif + + if (current_class->members_indices.has(member->identifier->name)) { + push_error(vformat(R"(%s "%s" has the same name as a previously declared %s.)", p_member_kind.capitalize(), member->identifier->name, current_class->get_member(member->identifier->name).get_type_name()), member->identifier); + } else { + current_class->add_member(member); + } } else { current_class->add_member(member); } } } -void GDScriptParser::parse_class_body() { +void GDScriptParser::parse_class_body(bool p_is_multiline) { bool class_end = false; while (!class_end && !is_at_end()) { switch (current.type) { @@ -817,6 +812,9 @@ void GDScriptParser::parse_class_body() { if (panic_mode) { synchronize(); } + if (!p_is_multiline) { + class_end = true; + } } } @@ -829,9 +827,24 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper return nullptr; } + GDScriptParser::IdentifierNode *identifier = parse_identifier(); + +#ifdef DEBUG_ENABLED + List<MethodInfo> gdscript_funcs; + GDScriptLanguage::get_singleton()->get_public_functions(&gdscript_funcs); + for (MethodInfo &info : gdscript_funcs) { + if (info.name == identifier->name) { + push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "local variable", identifier->name, "built-in function"); + } + } + if (Variant::has_utility_function(identifier->name)) { + push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "local variable", identifier->name, "built-in function"); + } +#endif + VariableNode *variable = alloc_node<VariableNode>(); - variable->identifier = parse_identifier(); - variable->export_info.name = variable->identifier->name; + variable->identifier = identifier; + variable->export_info.name = identifier->name; if (match(GDScriptTokenizer::Token::COLON)) { if (check(GDScriptTokenizer::Token::NEWLINE)) { @@ -965,7 +978,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_property(VariableNode *p_var void GDScriptParser::parse_property_setter(VariableNode *p_variable) { switch (p_variable->property) { - case VariableNode::PROP_INLINE: + case VariableNode::PROP_INLINE: { consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "set".)"); if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected parameter name after "(".)")) { p_variable->setter_parameter = parse_identifier(); @@ -973,9 +986,32 @@ void GDScriptParser::parse_property_setter(VariableNode *p_variable) { consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after parameter name.)*"); consume(GDScriptTokenizer::Token::COLON, R"*(Expected ":" after ")".)*"); - p_variable->setter = parse_suite("setter definition"); - break; + IdentifierNode *identifier = alloc_node<IdentifierNode>(); + identifier->name = "@" + p_variable->identifier->name + "_setter"; + + FunctionNode *function = alloc_node<FunctionNode>(); + function->identifier = identifier; + + FunctionNode *previous_function = current_function; + current_function = function; + + ParameterNode *parameter = alloc_node<ParameterNode>(); + parameter->identifier = p_variable->setter_parameter; + + if (parameter->identifier != nullptr) { + function->parameters_indices[parameter->identifier->name] = 0; + function->parameters.push_back(parameter); + SuiteNode *body = alloc_node<SuiteNode>(); + body->add_local(parameter, function); + + function->body = parse_suite("setter declaration", body); + p_variable->setter = function; + } + + current_function = previous_function; + break; + } case VariableNode::PROP_SETGET: consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "set")"); make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable); @@ -990,11 +1026,25 @@ void GDScriptParser::parse_property_setter(VariableNode *p_variable) { void GDScriptParser::parse_property_getter(VariableNode *p_variable) { switch (p_variable->property) { - case VariableNode::PROP_INLINE: + case VariableNode::PROP_INLINE: { consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "get".)"); - p_variable->getter = parse_suite("getter definition"); + IdentifierNode *identifier = alloc_node<IdentifierNode>(); + identifier->name = "@" + p_variable->identifier->name + "_getter"; + + FunctionNode *function = alloc_node<FunctionNode>(); + function->identifier = identifier; + + FunctionNode *previous_function = current_function; + current_function = function; + + SuiteNode *body = alloc_node<SuiteNode>(); + function->body = parse_suite("getter declaration", body); + + p_variable->getter = function; + current_function = previous_function; break; + } case VariableNode::PROP_SETGET: consume(GDScriptTokenizer::Token::EQUAL, R"(Expected "=" after "get")"); make_completion_context(COMPLETION_PROPERTY_METHOD, p_variable); @@ -1047,8 +1097,24 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() { return nullptr; } + GDScriptParser::IdentifierNode *identifier = parse_identifier(); +#ifdef DEBUG_ENABLED + List<MethodInfo> gdscript_funcs; + GDScriptLanguage::get_singleton()->get_public_functions(&gdscript_funcs); + for (MethodInfo &info : gdscript_funcs) { + if (info.name == identifier->name) { + push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "parameter", identifier->name, "built-in function"); + } + } + if (Variant::has_utility_function(identifier->name)) { + push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "parameter", identifier->name, "built-in function"); + } else if (ClassDB::class_exists(identifier->name)) { + push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "parameter", identifier->name, "global class"); + } +#endif + ParameterNode *parameter = alloc_node<ParameterNode>(); - parameter->identifier = parse_identifier(); + parameter->identifier = identifier; if (match(GDScriptTokenizer::Token::COLON)) { if (check((GDScriptTokenizer::Token::EQUAL))) { @@ -1077,7 +1143,9 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() { SignalNode *signal = alloc_node<SignalNode>(); signal->identifier = parse_identifier(); - if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { + if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { + push_multiline(true); + advance(); do { if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { // Allow for trailing comma. @@ -1100,6 +1168,7 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() { } } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end()); + pop_multiline(); consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected closing ")" after signal parameters.)*"); } @@ -1123,13 +1192,31 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { HashMap<StringName, int> elements; +#ifdef DEBUG_ENABLED + List<MethodInfo> gdscript_funcs; + GDScriptLanguage::get_singleton()->get_public_functions(&gdscript_funcs); +#endif + do { if (check(GDScriptTokenizer::Token::BRACE_CLOSE)) { break; // Allow trailing comma. } if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for enum key.)")) { EnumNode::Value item; - item.identifier = parse_identifier(); + GDScriptParser::IdentifierNode *identifier = parse_identifier(); +#ifdef DEBUG_ENABLED + for (MethodInfo &info : gdscript_funcs) { + if (info.name == identifier->name) { + push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "built-in function"); + } + } + if (Variant::has_utility_function(identifier->name)) { + push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "built-in function"); + } else if (ClassDB::class_exists(identifier->name)) { + push_warning(identifier, GDScriptWarning::SHADOWED_GLOBAL_IDENTIFIER, "enum member", identifier->name, "global class"); + } +#endif + item.identifier = identifier; item.parent_enum = enum_node; item.line = previous.start_line; item.leftmost_column = previous.leftmost_column; @@ -1158,7 +1245,6 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { } item.custom_value = value; } - item.rightmost_column = previous.rightmost_column; item.index = enum_node->values.size(); enum_node->values.push_back(item); @@ -1173,7 +1259,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)"); #ifdef TOOLS_ENABLED - // Enum values documentaion. + // Enum values documentation. for (int i = 0; i < enum_node->values.size(); i++) { if (i == enum_node->values.size() - 1) { // If close bracket is same line as last value. @@ -1338,8 +1424,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali } void GDScriptParser::clear_unused_annotations() { - for (const List<AnnotationNode *>::Element *E = annotation_stack.front(); E != nullptr; E = E->next()) { - AnnotationNode *annotation = E->get(); + for (const AnnotationNode *annotation : annotation_stack) { push_error(vformat(R"(Annotation "%s" does not precedes a valid target, so it will have no effect.)", annotation->name), annotation); } @@ -1384,6 +1469,9 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, int error_count = 0; do { + if (!multiline && previous.type == GDScriptTokenizer::Token::SEMICOLON && check(GDScriptTokenizer::Token::NEWLINE)) { + break; + } Node *statement = parse_statement(); if (statement == nullptr) { if (error_count++ > 100) { @@ -1424,7 +1512,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, break; } - } while (multiline && !check(GDScriptTokenizer::Token::DEDENT) && !lambda_ended && !is_at_end()); + } while ((multiline || previous.type == GDScriptTokenizer::Token::SEMICOLON) && !check(GDScriptTokenizer::Token::DEDENT) && !lambda_ended && !is_at_end()); if (multiline) { if (!lambda_ended) { @@ -1634,6 +1722,10 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { n_for->list = parse_expression(false); + if (!n_for->list) { + push_error(R"(Expected a list or range after "in".)"); + } + consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "for" condition.)"); // Save break/continue state. @@ -1725,6 +1817,7 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() { while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) { MatchBranchNode *branch = parse_match_branch(); if (branch == nullptr) { + advance(); continue; } @@ -1788,7 +1881,9 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { push_error(R"(No pattern found for "match" branch.)"); } - consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)"); + if (!consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "match" patterns.)")) { + return nullptr; + } // Save continue state. bool could_continue = can_continue; @@ -1802,8 +1897,8 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { List<StringName> binds; branch->patterns[0]->binds.get_key_list(&binds); - for (List<StringName>::Element *E = binds.front(); E != nullptr; E = E->next()) { - SuiteNode::Local local(branch->patterns[0]->binds[E->get()], current_function); + for (const StringName &E : binds) { + SuiteNode::Local local(branch->patterns[0]->binds[E], current_function); suite->add_local(local); } } @@ -1821,15 +1916,6 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ PatternNode *pattern = alloc_node<PatternNode>(); switch (current.type) { - case GDScriptTokenizer::Token::LITERAL: - advance(); - pattern->pattern_type = PatternNode::PT_LITERAL; - pattern->literal = parse_literal(); - if (pattern->literal == nullptr) { - // Error happened. - return nullptr; - } - break; case GDScriptTokenizer::Token::VAR: { // Bind. advance(); @@ -1892,44 +1978,44 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ // Dictionary. advance(); pattern->pattern_type = PatternNode::PT_DICTIONARY; - - if (!check(GDScriptTokenizer::Token::BRACE_CLOSE) && !is_at_end()) { - do { - if (match(GDScriptTokenizer::Token::PERIOD_PERIOD)) { - // Rest. + do { + if (check(GDScriptTokenizer::Token::BRACE_CLOSE) || is_at_end()) { + break; + } + if (match(GDScriptTokenizer::Token::PERIOD_PERIOD)) { + // Rest. + if (pattern->rest_used) { + push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)"); + } else { + PatternNode *sub_pattern = alloc_node<PatternNode>(); + sub_pattern->pattern_type = PatternNode::PT_REST; + pattern->dictionary.push_back({ nullptr, sub_pattern }); + pattern->rest_used = true; + } + } else { + ExpressionNode *key = parse_expression(false); + if (key == nullptr) { + push_error(R"(Expected expression as key for dictionary pattern.)"); + } + if (match(GDScriptTokenizer::Token::COLON)) { + // Value pattern. + PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern); + if (sub_pattern == nullptr) { + continue; + } if (pattern->rest_used) { push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)"); + } else if (sub_pattern->pattern_type == PatternNode::PT_REST) { + push_error(R"(The ".." pattern cannot be used as a value.)"); } else { - PatternNode *sub_pattern = alloc_node<PatternNode>(); - sub_pattern->pattern_type = PatternNode::PT_REST; - pattern->dictionary.push_back({ nullptr, sub_pattern }); - pattern->rest_used = true; + pattern->dictionary.push_back({ key, sub_pattern }); } } else { - ExpressionNode *key = parse_expression(false); - if (key == nullptr) { - push_error(R"(Expected expression as key for dictionary pattern.)"); - } - if (match(GDScriptTokenizer::Token::COLON)) { - // Value pattern. - PatternNode *sub_pattern = parse_match_pattern(p_root_pattern != nullptr ? p_root_pattern : pattern); - if (sub_pattern == nullptr) { - continue; - } - if (pattern->rest_used) { - push_error(R"(The ".." pattern must be the last element in the pattern dictionary.)"); - } else if (sub_pattern->pattern_type == PatternNode::PT_REST) { - push_error(R"(The ".." pattern cannot be used as a value.)"); - } else { - pattern->dictionary.push_back({ key, sub_pattern }); - } - } else { - // Key match only. - pattern->dictionary.push_back({ key, nullptr }); - } + // Key match only. + pattern->dictionary.push_back({ key, nullptr }); } - } while (match(GDScriptTokenizer::Token::COMMA)); - } + } + } while (match(GDScriptTokenizer::Token::COMMA)); consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected "}" to close the dictionary pattern.)"); break; } @@ -1938,8 +2024,13 @@ GDScriptParser::PatternNode *GDScriptParser::parse_match_pattern(PatternNode *p_ ExpressionNode *expression = parse_expression(false); if (expression == nullptr) { push_error(R"(Expected expression for match pattern.)"); + return nullptr; } else { - pattern->pattern_type = PatternNode::PT_EXPRESSION; + if (expression->type == GDScriptParser::Node::LITERAL) { + pattern->pattern_type = PatternNode::PT_LITERAL; + } else { + pattern->pattern_type = PatternNode::PT_EXPRESSION; + } pattern->expression = expression; } break; @@ -2129,10 +2220,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_builtin_constant(Expressio constant->value = Math_TAU; break; case GDScriptTokenizer::Token::CONST_INF: - constant->value = Math_INF; + constant->value = INFINITY; break; case GDScriptTokenizer::Token::CONST_NAN: - constant->value = Math_NAN; + constant->value = NAN; break; default: return nullptr; // Unreachable. @@ -2150,22 +2241,34 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_unary_operator(ExpressionN operation->operation = UnaryOpNode::OP_NEGATIVE; operation->variant_op = Variant::OP_NEGATE; operation->operand = parse_precedence(PREC_SIGN, false); + if (operation->operand == nullptr) { + push_error(R"(Expected expression after "-" operator.)"); + } break; case GDScriptTokenizer::Token::PLUS: operation->operation = UnaryOpNode::OP_POSITIVE; operation->variant_op = Variant::OP_POSITIVE; operation->operand = parse_precedence(PREC_SIGN, false); + if (operation->operand == nullptr) { + push_error(R"(Expected expression after "+" operator.)"); + } break; case GDScriptTokenizer::Token::TILDE: operation->operation = UnaryOpNode::OP_COMPLEMENT; operation->variant_op = Variant::OP_BIT_NEGATE; operation->operand = parse_precedence(PREC_BIT_NOT, false); + if (operation->operand == nullptr) { + push_error(R"(Expected expression after "~" operator.)"); + } break; case GDScriptTokenizer::Token::NOT: case GDScriptTokenizer::Token::BANG: operation->operation = UnaryOpNode::OP_LOGIC_NOT; operation->variant_op = Variant::OP_NOT; operation->operand = parse_precedence(PREC_LOGIC_NOT, false); + if (operation->operand == nullptr) { + push_error(vformat(R"(Expected expression after "%s" operator.)", op_type == GDScriptTokenizer::Token::NOT ? "not" : "!")); + } break; default: return nullptr; // Unreachable. @@ -2302,6 +2405,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_ternary_operator(Expressio operation->false_expr = parse_precedence(PREC_TERNARY, false); + if (operation->false_expr == nullptr) { + push_error(R"(Expected expression after "else".)"); + } + return operation; } @@ -2408,10 +2515,17 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode } assignment->assignee = p_previous_operand; assignment->assigned_value = parse_expression(false); + if (assignment->assigned_value == nullptr) { + push_error(R"(Expected an expression after "=".)"); + } #ifdef DEBUG_ENABLED - if (has_operator && source_variable != nullptr && source_variable->assignments == 0) { - push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name); + if (source_variable != nullptr) { + if (has_operator && source_variable->assignments == 0) { + push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name); + } + + source_variable->assignments += 1; } #endif @@ -2420,9 +2534,15 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode GDScriptParser::ExpressionNode *GDScriptParser::parse_await(ExpressionNode *p_previous_operand, bool p_can_assign) { AwaitNode *await = alloc_node<AwaitNode>(); - await->to_await = parse_precedence(PREC_AWAIT, false); + ExpressionNode *element = parse_precedence(PREC_AWAIT, false); + if (element == nullptr) { + push_error(R"(Expected signal or coroutine after "await".)"); + } + await->to_await = element; - current_function->is_coroutine = true; + if (current_function) { // Might be null in a getter or setter. + current_function->is_coroutine = true; + } return await; } @@ -2486,8 +2606,15 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode switch (dictionary->style) { case DictionaryNode::LUA_TABLE: - if (key != nullptr && key->type != Node::IDENTIFIER) { - push_error("Expected identifier as dictionary key."); + if (key != nullptr && key->type != Node::IDENTIFIER && key->type != Node::LITERAL) { + push_error("Expected identifier or string as LUA-style dictionary key."); + advance(); + break; + } + if (key != nullptr && key->type == Node::LITERAL && static_cast<LiteralNode *>(key)->value.get_type() != Variant::STRING) { + push_error("Expected identifier or string as LUA-style dictionary key."); + advance(); + break; } if (!match(GDScriptTokenizer::Token::EQUAL)) { if (match(GDScriptTokenizer::Token::COLON)) { @@ -2497,8 +2624,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_dictionary(ExpressionNode push_error(R"(Expected "=" after dictionary key.)"); } } - key->is_constant = true; - key->reduced_value = static_cast<IdentifierNode *>(key)->name; + if (key != nullptr) { + key->is_constant = true; + if (key->type == Node::IDENTIFIER) { + key->reduced_value = static_cast<IdentifierNode *>(key)->name; + } else if (key->type == Node::LITERAL) { + key->reduced_value = StringName(static_cast<LiteralNode *>(key)->value.operator String()); + } + } break; case DictionaryNode::PYTHON_DICT: if (!match(GDScriptTokenizer::Token::COLON)) { @@ -2577,6 +2710,10 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode * subscript->base = p_previous_operand; subscript->index = parse_expression(false); + if (subscript->index == nullptr) { + push_error(R"(Expected expression after "[".)"); + } + pop_multiline(); consume(GDScriptTokenizer::Token::BRACKET_CLOSE, R"(Expected "]" after subscription index.)"); @@ -2702,6 +2839,23 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p get_node->chain.push_back(identifier); } while (match(GDScriptTokenizer::Token::SLASH)); return get_node; + } else if (match(GDScriptTokenizer::Token::SLASH)) { + GetNodeNode *get_node = alloc_node<GetNodeNode>(); + IdentifierNode *identifier_root = alloc_node<IdentifierNode>(); + get_node->chain.push_back(identifier_root); + int chain_position = 0; + do { + make_completion_context(COMPLETION_GET_NODE, get_node, chain_position++); + if (!current.is_node_name()) { + push_error(R"(Expect node path after "/".)"); + return nullptr; + } + advance(); + IdentifierNode *identifier = alloc_node<IdentifierNode>(); + identifier->name = previous.get_identifier(); + get_node->chain.push_back(identifier); + } while (match(GDScriptTokenizer::Token::SLASH)); + return get_node; } else { push_error(R"(Expect node path as string or identifier after "$".)"); return nullptr; @@ -2791,6 +2945,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_lambda(ExpressionNode *p_p return lambda; } +GDScriptParser::ExpressionNode *GDScriptParser::parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign) { + push_error(R"("yield" was removed in Godot 4.0. Use "await" instead.)"); + return nullptr; +} + GDScriptParser::ExpressionNode *GDScriptParser::parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign) { // Just for better error messages. GDScriptTokenizer::Token::Type invalid = previous.type; @@ -2991,9 +3150,9 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & } else { /* Syntax: - @tutorial ( The Title Here ) : http://the.url/ - ^ open ^ close ^ colon ^ url - */ + * @tutorial ( The Title Here ) : https://the.url/ + * ^ open ^ close ^ colon ^ url + */ int open_bracket_pos = begin_scan, close_bracket_pos = 0; while (open_bracket_pos < striped_line.length() && (striped_line[open_bracket_pos] == ' ' || striped_line[open_bracket_pos] == '\t')) { open_bracket_pos++; @@ -3147,7 +3306,7 @@ GDScriptParser::ParseRule *GDScriptParser::get_rule(GDScriptTokenizer::Token::Ty { nullptr, nullptr, PREC_NONE }, // TRAIT, { nullptr, nullptr, PREC_NONE }, // VAR, { nullptr, nullptr, PREC_NONE }, // VOID, - { nullptr, nullptr, PREC_NONE }, // YIELD, + { &GDScriptParser::parse_yield, nullptr, PREC_NONE }, // YIELD, // Punctuation { &GDScriptParser::parse_array, &GDScriptParser::parse_subscript, PREC_SUBSCRIPT }, // BRACKET_OPEN, { nullptr, nullptr, PREC_NONE }, // BRACKET_CLOSE, @@ -3255,7 +3414,7 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) p_annotation->resolved_arguments.push_back(r); if (error.error != Callable::CallError::CALL_OK) { push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); - p_annotation->resolved_arguments.remove(p_annotation->resolved_arguments.size() - 1); + p_annotation->resolved_arguments.remove_at(p_annotation->resolved_arguments.size() - 1); return false; } break; @@ -3279,7 +3438,7 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) p_annotation->resolved_arguments.push_back(r); if (error.error != Callable::CallError::CALL_OK) { push_error(vformat(R"(Expected %s as argument %d of annotation "%s").)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); - p_annotation->resolved_arguments.remove(p_annotation->resolved_arguments.size() - 1); + p_annotation->resolved_arguments.remove_at(p_annotation->resolved_arguments.size() - 1); return false; } break; @@ -3368,10 +3527,10 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node variable->export_info.hint_string = Variant::get_type_name(export_type.builtin_type); break; case GDScriptParser::DataType::NATIVE: - if (ClassDB::is_parent_class(get_real_class_name(export_type.native_type), "Resource")) { + if (ClassDB::is_parent_class(export_type.native_type, "Resource")) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; - variable->export_info.hint_string = get_real_class_name(export_type.native_type); + variable->export_info.hint_string = export_type.native_type; } else { push_error(R"(Export type can only be built-in, a resource, or an enum.)", variable); return false; @@ -3383,7 +3542,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node String enum_hint_string; for (const Map<StringName, int>::Element *E = export_type.enum_values.front(); E; E = E->next()) { - enum_hint_string += E->key().operator String().camelcase_to_underscore(true).capitalize().xml_escape(); + enum_hint_string += E->key().operator String().capitalize().xml_escape(); enum_hint_string += ":"; enum_hint_string += String::num_int64(E->get()).xml_escape(); @@ -3427,31 +3586,56 @@ bool GDScriptParser::warning_annotations(const AnnotationNode *p_annotation, Nod ERR_FAIL_V_MSG(false, "Not implemented."); } -template <MultiplayerAPI::RPCMode t_mode> +template <Multiplayer::RPCMode t_mode> bool GDScriptParser::network_annotations(const AnnotationNode *p_annotation, Node *p_node) { ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE && p_node->type != Node::FUNCTION, false, vformat(R"("%s" annotation can only be applied to variables and functions.)", p_annotation->name)); - switch (p_node->type) { - case Node::VARIABLE: { - VariableNode *variable = static_cast<VariableNode *>(p_node); - if (variable->rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) { - push_error(R"(RPC annotations can only be used once per variable.)", p_annotation); + Multiplayer::RPCConfig rpc_config; + rpc_config.rpc_mode = t_mode; + if (p_annotation->resolved_arguments.size()) { + int last = p_annotation->resolved_arguments.size() - 1; + if (p_annotation->resolved_arguments[last].get_type() == Variant::INT) { + rpc_config.channel = p_annotation->resolved_arguments[last].operator int(); + last -= 1; + } + if (last > 3) { + push_error(R"(Invalid RPC arguments. At most 4 arguments are allowed, where only the last argument can be an integer to specify the channel.')", p_annotation); + return false; + } + for (int i = last; i >= 0; i--) { + String mode = p_annotation->resolved_arguments[i].operator String(); + if (mode == "any_peer") { + rpc_config.rpc_mode = Multiplayer::RPC_MODE_ANY_PEER; + } else if (mode == "authority") { + rpc_config.rpc_mode = Multiplayer::RPC_MODE_AUTHORITY; + } else if (mode == "call_local") { + rpc_config.call_local = true; + } else if (mode == "call_remote") { + rpc_config.call_local = false; + } else if (mode == "reliable") { + rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_RELIABLE; + } else if (mode == "unreliable") { + rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_UNRELIABLE; + } else if (mode == "unreliable_ordered") { + rpc_config.transfer_mode = Multiplayer::TRANSFER_MODE_UNRELIABLE_ORDERED; + } else { + push_error(R"(Invalid RPC argument. Must be one of: 'call_local'/'call_remote' (local calls), 'any_peer'/'authority' (permission), 'reliable'/'unreliable'/'unreliable_ordered' (transfer mode).)", p_annotation); } - variable->rpc_mode = t_mode; - break; } + } + switch (p_node->type) { case Node::FUNCTION: { FunctionNode *function = static_cast<FunctionNode *>(p_node); - if (function->rpc_mode != MultiplayerAPI::RPC_MODE_DISABLED) { + if (function->rpc_config.rpc_mode != Multiplayer::RPC_MODE_DISABLED) { push_error(R"(RPC annotations can only be used once per function.)", p_annotation); + return false; } - function->rpc_mode = t_mode; + function->rpc_config = rpc_config; break; } default: return false; // Unreachable. } - return true; } @@ -3547,6 +3731,39 @@ String GDScriptParser::DataType::to_string() const { ERR_FAIL_V_MSG("<unresolved type", "Kind set outside the enum range."); } +static Variant::Type _variant_type_to_typed_array_element_type(Variant::Type p_type) { + switch (p_type) { + case Variant::PACKED_BYTE_ARRAY: + case Variant::PACKED_INT32_ARRAY: + case Variant::PACKED_INT64_ARRAY: + return Variant::INT; + case Variant::PACKED_FLOAT32_ARRAY: + case Variant::PACKED_FLOAT64_ARRAY: + return Variant::FLOAT; + case Variant::PACKED_STRING_ARRAY: + return Variant::STRING; + case Variant::PACKED_VECTOR2_ARRAY: + return Variant::VECTOR2; + case Variant::PACKED_VECTOR3_ARRAY: + return Variant::VECTOR3; + case Variant::PACKED_COLOR_ARRAY: + return Variant::COLOR; + default: + return Variant::NIL; + } +} + +bool GDScriptParser::DataType::is_typed_container_type() const { + return kind == GDScriptParser::DataType::BUILTIN && _variant_type_to_typed_array_element_type(builtin_type) != Variant::NIL; +} + +GDScriptParser::DataType GDScriptParser::DataType::get_typed_container_type() const { + GDScriptParser::DataType type; + type.kind = GDScriptParser::DataType::BUILTIN; + type.builtin_type = _variant_type_to_typed_array_element_type(builtin_type); + return type; +} + /*---------- PRETTY PRINT FOR DEBUG ----------*/ #ifdef DEBUG_ENABLED @@ -3591,7 +3808,7 @@ void GDScriptParser::TreePrinter::push_text(const String &p_text) { printed += p_text; } -void GDScriptParser::TreePrinter::print_annotation(AnnotationNode *p_annotation) { +void GDScriptParser::TreePrinter::print_annotation(const AnnotationNode *p_annotation) { push_text(p_annotation->name); push_text(" ("); for (int i = 0; i < p_annotation->arguments.size(); i++) { @@ -3966,8 +4183,8 @@ void GDScriptParser::TreePrinter::print_for(ForNode *p_for) { } void GDScriptParser::TreePrinter::print_function(FunctionNode *p_function, const String &p_context) { - for (const List<AnnotationNode *>::Element *E = p_function->annotations.front(); E != nullptr; E = E->next()) { - print_annotation(E->get()); + for (const AnnotationNode *E : p_function->annotations) { + print_annotation(E); } push_text(p_context); push_text(" "); @@ -4306,8 +4523,8 @@ void GDScriptParser::TreePrinter::print_unary_op(UnaryOpNode *p_unary_op) { } void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) { - for (const List<AnnotationNode *>::Element *E = p_variable->annotations.front(); E != nullptr; E = E->next()) { - print_annotation(E->get()); + for (const AnnotationNode *E : p_variable->annotations) { + print_annotation(E); } push_text("Variable "); @@ -4339,7 +4556,7 @@ void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) { if (p_variable->property == VariableNode::PROP_INLINE) { push_line(":"); increase_indent(); - print_suite(p_variable->getter); + print_suite(p_variable->getter->body); decrease_indent(); } else { push_line(" ="); @@ -4359,7 +4576,7 @@ void GDScriptParser::TreePrinter::print_variable(VariableNode *p_variable) { } push_line("):"); increase_indent(); - print_suite(p_variable->setter); + print_suite(p_variable->setter->body); decrease_indent(); } else { push_line(" ="); @@ -4398,7 +4615,7 @@ void GDScriptParser::TreePrinter::print_tree(const GDScriptParser &p_parser) { } print_class(p_parser.get_tree()); - print_line(printed); + print_line(String(printed)); } #endif // DEBUG_ENABLED diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index b1b29a7bd1..af9b973ada 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -31,9 +31,9 @@ #ifndef GDSCRIPT_PARSER_H #define GDSCRIPT_PARSER_H -#include "core/io/multiplayer_api.h" #include "core/io/resource.h" -#include "core/object/reference.h" +#include "core/multiplayer/multiplayer.h" +#include "core/object/ref_counted.h" #include "core/object/script_language.h" #include "core/string/string_name.h" #include "core/string/ustring.h" @@ -161,6 +161,10 @@ public: container_element_type = nullptr; } + bool is_typed_container_type() const; + + GDScriptParser::DataType get_typed_container_type() const; + 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. @@ -370,6 +374,7 @@ public: Variant::Operator variant_op = Variant::OP_MAX; ExpressionNode *assignee = nullptr; ExpressionNode *assigned_value = nullptr; + bool use_conversion_assign = false; AssignmentNode() { type = ASSIGNMENT; @@ -728,7 +733,7 @@ public: SuiteNode *body = nullptr; bool is_static = false; bool is_coroutine = false; - MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; + Multiplayer::RPCConfig rpc_config; MethodInfo info; LambdaNode *source_lambda = nullptr; #ifdef TOOLS_ENABLED @@ -1104,21 +1109,21 @@ public: PropertyStyle property = PROP_NONE; union { - SuiteNode *setter = nullptr; + FunctionNode *setter = nullptr; IdentifierNode *setter_pointer; }; IdentifierNode *setter_parameter = nullptr; union { - SuiteNode *getter = nullptr; + FunctionNode *getter = nullptr; IdentifierNode *getter_pointer; }; bool exported = false; bool onready = false; PropertyInfo export_info; - MultiplayerAPI::RPCMode rpc_mode = MultiplayerAPI::RPC_MODE_DISABLED; int assignments = 0; int usages = 0; + bool use_conversion_assign = false; #ifdef TOOLS_ENABLED String doc_description; #endif // TOOLS_ENABLED @@ -1319,7 +1324,7 @@ private: ClassNode *parse_class(); void parse_class_name(); void parse_extends(); - void parse_class_body(); + void parse_class_body(bool p_is_multiline); template <class T> void parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind); SignalNode *parse_signal(); @@ -1339,7 +1344,7 @@ private: template <PropertyHint t_hint, Variant::Type t_type> bool export_annotations(const AnnotationNode *p_annotation, Node *p_target); bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target); - template <MultiplayerAPI::RPCMode t_mode> + template <Multiplayer::RPCMode t_mode> bool network_annotations(const AnnotationNode *p_annotation, Node *p_target); // Statements. Node *parse_statement(); @@ -1383,6 +1388,7 @@ private: ExpressionNode *parse_attribute(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_subscript(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_lambda(ExpressionNode *p_previous_operand, bool p_can_assign); + ExpressionNode *parse_yield(ExpressionNode *p_previous_operand, bool p_can_assign); ExpressionNode *parse_invalid_token(ExpressionNode *p_previous_operand, bool p_can_assign); TypeNode *parse_type(bool p_allow_void = false); #ifdef TOOLS_ENABLED @@ -1398,7 +1404,6 @@ public: ClassNode *get_tree() const { return head; } bool is_tool() const { return _is_tool; } static Variant::Type get_builtin_type(const StringName &p_type); - static StringName get_real_class_name(const StringName &p_source); CompletionContext get_completion_context() const { return completion_context; } CompletionCall get_completion_call() const { return completion_call; } @@ -1430,7 +1435,7 @@ public: void push_line(const String &p_line = String()); void push_text(const String &p_text); - void print_annotation(AnnotationNode *p_annotation); + void print_annotation(const AnnotationNode *p_annotation); void print_array(ArrayNode *p_array); void print_assert(AssertNode *p_assert); void print_assignment(AssignmentNode *p_assignment); diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 3f14156dfa..3725fb58f5 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -795,6 +795,15 @@ 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."); + error.start_column = column; + error.leftmost_column = error.start_column; + error.end_column = column + 1; + error.rightmost_column = error.end_column; + push_error(error); + } + if (ch == '\\') { // Escape pattern. _advance(); @@ -1064,7 +1073,8 @@ void GDScriptTokenizer::check_indent() { // First time indenting, choose character now. indent_char = current_indent_char; } else if (current_indent_char != indent_char) { - Token error = make_error(vformat("Used \"%s\" for indentation instead \"%s\" as used before in the file.", String(¤t_indent_char, 1).c_escape(), String(&indent_char, 1).c_escape())); + Token error = make_error(vformat("Used %s character for indentation instead of %s as used before in the file.", + _get_indent_char_name(current_indent_char), _get_indent_char_name(indent_char))); error.start_line = line; error.start_column = 1; error.leftmost_column = 1; @@ -1114,6 +1124,12 @@ void GDScriptTokenizer::check_indent() { } } +String GDScriptTokenizer::_get_indent_char_name(char32_t ch) { + ERR_FAIL_COND_V(ch != ' ' && ch != '\t', String(&ch, 1).c_escape()); + + return ch == ' ' ? "space" : "tab"; +} + void GDScriptTokenizer::_skip_whitespace() { if (pending_indents != 0) { // Still have some indent/dedent tokens to give. @@ -1440,7 +1456,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { GDScriptTokenizer::GDScriptTokenizer() { #ifdef TOOLS_ENABLED if (EditorSettings::get_singleton()) { - tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size"); + tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size"); } #endif // TOOLS_ENABLED } diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index 84b82c07f0..b4ee11fd9a 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -233,6 +233,7 @@ private: bool has_error() const { return !error_stack.is_empty(); } Token pop_error(); char32_t _advance(); + String _get_indent_char_name(char32_t ch); void _skip_whitespace(); void check_indent(); diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index 64c629662c..e997d3a51e 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -317,9 +317,9 @@ struct GDScriptUtilityFunctionsDefinitions { d["@subpath"] = cp; d["@path"] = p->get_path(); - for (Map<StringName, GDScript::MemberInfo>::Element *E = base->member_indices.front(); E; E = E->next()) { - if (!d.has(E->key())) { - d[E->key()] = ins->members[E->get().index]; + for (const KeyValue<StringName, GDScript::MemberInfo> &E : base->member_indices) { + if (!d.has(E.key)) { + d[E.key] = ins->members[E.value.index]; } } *r_ret = d; @@ -396,9 +396,9 @@ struct GDScriptUtilityFunctionsDefinitions { GDScriptInstance *ins = static_cast<GDScriptInstance *>(static_cast<Object *>(*r_ret)->get_script_instance()); Ref<GDScript> gd_ref = ins->get_script(); - for (Map<StringName, GDScript::MemberInfo>::Element *E = gd_ref->member_indices.front(); E; E = E->next()) { - if (d.has(E->key())) { - ins->members.write[E->get().index] = d[E->key()]; + for (KeyValue<StringName, GDScript::MemberInfo> &E : gd_ref->member_indices) { + if (d.has(E.key)) { + ins->members.write[E.value.index] = d[E.key]; } } } @@ -437,9 +437,13 @@ struct GDScriptUtilityFunctionsDefinitions { str += p_args[i]->operator String(); } - ScriptLanguage *script = GDScriptLanguage::get_singleton(); - if (script->debug_get_stack_level_count() > 0) { - str += "\n At: " + script->debug_get_stack_level_source(0) + ":" + itos(script->debug_get_stack_level_line(0)) + ":" + script->debug_get_stack_level_function(0) + "()"; + if (Thread::get_caller_id() == Thread::get_main_id()) { + ScriptLanguage *script = GDScriptLanguage::get_singleton(); + if (script->debug_get_stack_level_count() > 0) { + str += "\n At: " + script->debug_get_stack_level_source(0) + ":" + itos(script->debug_get_stack_level_line(0)) + ":" + script->debug_get_stack_level_function(0) + "()"; + } + } else { + str += "\n At: Cannot retrieve debug info outside the main thread. Thread ID: " + itos(Thread::get_caller_id()); } print_line(str); @@ -448,15 +452,24 @@ struct GDScriptUtilityFunctionsDefinitions { static inline void print_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { VALIDATE_ARG_COUNT(0); + if (Thread::get_caller_id() != Thread::get_main_id()) { + print_line("Cannot retrieve debug info outside the main thread. Thread ID: " + itos(Thread::get_caller_id())); + return; + } ScriptLanguage *script = GDScriptLanguage::get_singleton(); for (int i = 0; i < script->debug_get_stack_level_count(); i++) { print_line("Frame " + itos(i) + " - " + script->debug_get_stack_level_source(i) + ":" + itos(script->debug_get_stack_level_line(i)) + " in function '" + script->debug_get_stack_level_function(i) + "'"); }; + *r_ret = Variant(); } static inline void get_stack(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { VALIDATE_ARG_COUNT(0); + if (Thread::get_caller_id() != Thread::get_main_id()) { + *r_ret = Array(); + return; + } ScriptLanguage *script = GDScriptLanguage::get_singleton(); Array ret; @@ -706,8 +719,8 @@ bool GDScriptUtilityFunctions::function_exists(const StringName &p_function) { } void GDScriptUtilityFunctions::get_function_list(List<StringName> *r_functions) { - for (const List<StringName>::Element *E = utility_function_name_table.front(); E; E = E->next()) { - r_functions->push_back(E->get()); + for (const StringName &E : utility_function_name_table) { + r_functions->push_back(E); } } diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 4757ec6ca9..6dd8c3e0dd 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -88,9 +88,9 @@ static String _get_var_type(const Variant *p_var) { Object *bobj = p_var->get_validated_object_with_check(was_freed); if (!bobj) { if (was_freed) { - basestr = "null instance"; - } else { basestr = "previously freed"; + } else { + basestr = "null instance"; } } else { basestr = bobj->get_class(); @@ -152,6 +152,44 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const return err_text; } +void (*type_init_function_table[])(Variant *) = { + nullptr, // NIL (shouldn't be called). + &VariantInitializer<bool>::init, // BOOL. + &VariantInitializer<int64_t>::init, // INT. + &VariantInitializer<double>::init, // FLOAT. + &VariantInitializer<String>::init, // STRING. + &VariantInitializer<Vector2>::init, // VECTOR2. + &VariantInitializer<Vector2i>::init, // VECTOR2I. + &VariantInitializer<Rect2>::init, // RECT2. + &VariantInitializer<Rect2i>::init, // RECT2I. + &VariantInitializer<Vector3>::init, // VECTOR3. + &VariantInitializer<Vector3i>::init, // VECTOR3I. + &VariantInitializer<Transform2D>::init, // TRANSFORM2D. + &VariantInitializer<Plane>::init, // PLANE. + &VariantInitializer<Quaternion>::init, // QUATERNION. + &VariantInitializer<AABB>::init, // AABB. + &VariantInitializer<Basis>::init, // BASIS. + &VariantInitializer<Transform3D>::init, // TRANSFORM3D. + &VariantInitializer<Color>::init, // COLOR. + &VariantInitializer<StringName>::init, // STRING_NAME. + &VariantInitializer<NodePath>::init, // NODE_PATH. + &VariantInitializer<RID>::init, // RID. + &VariantInitializer<Object *>::init, // OBJECT. + &VariantInitializer<Callable>::init, // CALLABLE. + &VariantInitializer<Signal>::init, // SIGNAL. + &VariantInitializer<Dictionary>::init, // DICTIONARY. + &VariantInitializer<Array>::init, // ARRAY. + &VariantInitializer<PackedByteArray>::init, // PACKED_BYTE_ARRAY. + &VariantInitializer<PackedInt32Array>::init, // PACKED_INT32_ARRAY. + &VariantInitializer<PackedInt64Array>::init, // PACKED_INT64_ARRAY. + &VariantInitializer<PackedFloat32Array>::init, // PACKED_FLOAT32_ARRAY. + &VariantInitializer<PackedFloat64Array>::init, // PACKED_FLOAT64_ARRAY. + &VariantInitializer<PackedStringArray>::init, // PACKED_STRING_ARRAY. + &VariantInitializer<PackedVector2Array>::init, // PACKED_VECTOR2_ARRAY. + &VariantInitializer<PackedVector3Array>::init, // PACKED_VECTOR3_ARRAY. + &VariantInitializer<PackedColorArray>::init, // PACKED_COLOR_ARRAY. +}; + #if defined(__GNUC__) #define OPCODES_TABLE \ static const void *switch_table_ops[] = { \ @@ -196,6 +234,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const &&OPCODE_CALL_SELF_BASE, \ &&OPCODE_CALL_METHOD_BIND, \ &&OPCODE_CALL_METHOD_BIND_RET, \ + &&OPCODE_CALL_BUILTIN_STATIC, \ &&OPCODE_CALL_PTRCALL_NO_RETURN, \ &&OPCODE_CALL_PTRCALL_BOOL, \ &&OPCODE_CALL_PTRCALL_INT, \ @@ -209,10 +248,10 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const &&OPCODE_CALL_PTRCALL_VECTOR3I, \ &&OPCODE_CALL_PTRCALL_TRANSFORM2D, \ &&OPCODE_CALL_PTRCALL_PLANE, \ - &&OPCODE_CALL_PTRCALL_QUAT, \ + &&OPCODE_CALL_PTRCALL_QUATERNION, \ &&OPCODE_CALL_PTRCALL_AABB, \ &&OPCODE_CALL_PTRCALL_BASIS, \ - &&OPCODE_CALL_PTRCALL_TRANSFORM, \ + &&OPCODE_CALL_PTRCALL_TRANSFORM3D, \ &&OPCODE_CALL_PTRCALL_COLOR, \ &&OPCODE_CALL_PTRCALL_STRING_NAME, \ &&OPCODE_CALL_PTRCALL_NODE_PATH, \ @@ -283,6 +322,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const &&OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, \ &&OPCODE_ITERATE_PACKED_COLOR_ARRAY, \ &&OPCODE_ITERATE_OBJECT, \ + &&OPCODE_STORE_GLOBAL, \ &&OPCODE_STORE_NAMED_GLOBAL, \ &&OPCODE_TYPE_ADJUST_BOOL, \ &&OPCODE_TYPE_ADJUST_INT, \ @@ -296,7 +336,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const &&OPCODE_TYPE_ADJUST_VECTOR3I, \ &&OPCODE_TYPE_ADJUST_TRANSFORM2D, \ &&OPCODE_TYPE_ADJUST_PLANE, \ - &&OPCODE_TYPE_ADJUST_QUAT, \ + &&OPCODE_TYPE_ADJUST_QUATERNION, \ &&OPCODE_TYPE_ADJUST_AABB, \ &&OPCODE_TYPE_ADJUST_BASIS, \ &&OPCODE_TYPE_ADJUST_TRANSFORM, \ @@ -359,7 +399,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const #define OP_GET_VECTOR3I get_vector3i #define OP_GET_RECT2 get_rect2 #define OP_GET_RECT2I get_rect2i -#define OP_GET_QUAT get_quat +#define OP_GET_QUATERNION get_quaternion #define OP_GET_COLOR get_color #define OP_GET_STRING get_string #define OP_GET_STRING_NAME get_string_name @@ -377,7 +417,7 @@ String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const #define OP_GET_PACKED_VECTOR2_ARRAY get_vector2_array #define OP_GET_PACKED_VECTOR3_ARRAY get_vector3_array #define OP_GET_PACKED_COLOR_ARRAY get_color_array -#define OP_GET_TRANSFORM get_transform +#define OP_GET_TRANSFORM3D get_transform #define OP_GET_TRANSFORM2D get_transform2d #define OP_GET_PLANE get_plane #define OP_GET_AABB get_aabb @@ -491,6 +531,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a memnew_placement(&stack[ADDR_STACK_CLASS], Variant(script)); + for (const KeyValue<int, Variant::Type> &E : temporary_slots) { + type_init_function_table[E.value](&stack[E.key]); + } + String err_text; #ifdef DEBUG_ENABLED @@ -1066,7 +1110,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } else { #ifdef DEBUG_ENABLED err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + - "' to a variable of type '" + Variant::get_type_name(var_type) + "'."; + "' to a variable of type '" + Variant::get_type_name(var_type) + "'."; OPCODE_BREAK; } } else { @@ -1088,7 +1132,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (src->get_type() != Variant::ARRAY) { #ifdef DEBUG_ENABLED err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + - "' to a variable of type '" + +"'."; + "' to a variable of type '" + +"'."; #endif OPCODE_BREAK; } @@ -1114,14 +1158,14 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(!nc); if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { err_text = "Trying to assign value of type '" + Variant::get_type_name(src->get_type()) + - "' to a variable of type '" + nc->get_name() + "'."; + "' to a variable of type '" + nc->get_name() + "'."; OPCODE_BREAK; } Object *src_obj = src->operator Object *(); if (src_obj && !ClassDB::is_parent_class(src_obj->get_class_name(), nc->get_name())) { err_text = "Trying to assign value of type '" + src_obj->get_class_name() + - "' to a variable of type '" + nc->get_name() + "'."; + "' to a variable of type '" + nc->get_name() + "'."; OPCODE_BREAK; } #endif // DEBUG_ENABLED @@ -1151,7 +1195,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a ScriptInstance *scr_inst = src->operator Object *()->get_script_instance(); if (!scr_inst) { err_text = "Trying to assign value of type '" + src->operator Object *()->get_class_name() + - "' to a variable of type '" + base_type->get_path().get_file() + "'."; + "' to a variable of type '" + base_type->get_path().get_file() + "'."; OPCODE_BREAK; } @@ -1168,7 +1212,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (!valid) { err_text = "Trying to assign value of type '" + src->operator Object *()->get_script_instance()->get_script()->get_path().get_file() + - "' to a variable of type '" + base_type->get_path().get_file() + "'."; + "' to a variable of type '" + base_type->get_path().get_file() + "'."; OPCODE_BREAK; } } @@ -1188,6 +1232,13 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(to_type < 0 || to_type >= Variant::VARIANT_MAX); +#ifdef DEBUG_ENABLED + if (src->operator Object *() && !src->get_validated_object()) { + err_text = "Trying to cast a freed object."; + OPCODE_BREAK; + } +#endif + Callable::CallError err; Variant::construct(to_type, *dst, (const Variant **)&src, 1, err); @@ -1212,6 +1263,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(!nc); #ifdef DEBUG_ENABLED + if (src->operator Object *() && !src->get_validated_object()) { + err_text = "Trying to cast a freed object."; + OPCODE_BREAK; + } if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { err_text = "Invalid cast: can't convert a non-object value to an object type."; OPCODE_BREAK; @@ -1240,6 +1295,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(!base_type); #ifdef DEBUG_ENABLED + if (src->operator Object *() && !src->get_validated_object()) { + err_text = "Trying to cast a freed object."; + OPCODE_BREAK; + } if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { err_text = "Trying to assign a non-object value to a variable of type '" + base_type->get_path().get_file() + "'."; OPCODE_BREAK; @@ -1573,6 +1632,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_CALL_BUILTIN_STATIC) { + CHECK_SPACE(4 + instr_arg_count); + + ip += instr_arg_count; + + GD_ERR_BREAK(_code_ptr[ip + 1] < 0 || _code_ptr[ip + 1] >= Variant::VARIANT_MAX); + Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 1]; + + int methodname_idx = _code_ptr[ip + 2]; + GD_ERR_BREAK(methodname_idx < 0 || methodname_idx >= _global_names_count); + const StringName *methodname = &_global_names_ptr[methodname_idx]; + + int argc = _code_ptr[ip + 3]; + GD_ERR_BREAK(argc < 0); + + GET_INSTRUCTION_ARG(ret, argc); + + const Variant **argptrs = const_cast<const Variant **>(instruction_args); + +#ifdef DEBUG_ENABLED + uint64_t call_time = 0; + + if (GDScriptLanguage::get_singleton()->profiling) { + call_time = OS::get_singleton()->get_ticks_usec(); + } +#endif + + Callable::CallError err; + Variant::call_static(builtin_type, *methodname, argptrs, argc, *ret, err); + +#ifdef DEBUG_ENABLED + if (GDScriptLanguage::get_singleton()->profiling) { + function_call_time += OS::get_singleton()->get_ticks_usec() - call_time; + } + + if (err.error != Callable::CallError::CALL_OK) { + err_text = _get_call_error(err, "static function '" + methodname->operator String() + "' in type '" + Variant::get_type_name(builtin_type) + "'", argptrs); + OPCODE_BREAK; + } +#endif + + ip += 4; + } + DISPATCH_OPCODE; + #ifdef DEBUG_ENABLED #define OPCODE_CALL_PTR(m_type) \ OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \ @@ -1615,6 +1719,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #define OPCODE_CALL_PTR(m_type) \ OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \ 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]]; \ @@ -1645,10 +1750,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE_CALL_PTR(VECTOR3I); OPCODE_CALL_PTR(TRANSFORM2D); OPCODE_CALL_PTR(PLANE); - OPCODE_CALL_PTR(QUAT); + OPCODE_CALL_PTR(QUATERNION); OPCODE_CALL_PTR(AABB); OPCODE_CALL_PTR(BASIS); - OPCODE_CALL_PTR(TRANSFORM); + OPCODE_CALL_PTR(TRANSFORM3D); OPCODE_CALL_PTR(COLOR); OPCODE_CALL_PTR(STRING_NAME); OPCODE_CALL_PTR(NODE_PATH); @@ -1882,7 +1987,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED if (err.error != Callable::CallError::CALL_OK) { // TODO: Add this information in debug. - String methodstr = "<unkown function>"; + String methodstr = "<unknown function>"; if (dst->get_type() == Variant::STRING) { // Call provided error string. err_text = "Error calling GDScript utility function '" + methodstr + "': " + String(*dst); @@ -1901,7 +2006,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a ip += instr_arg_count; - int self_fun = _code_ptr[ip + 1]; + int argc = _code_ptr[ip + 1]; + GD_ERR_BREAK(argc < 0); + + int self_fun = _code_ptr[ip + 2]; #ifdef DEBUG_ENABLED if (self_fun < 0 || self_fun >= _global_names_count) { err_text = "compiler bug, function name not found"; @@ -1910,9 +2018,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #endif const StringName *methodname = &_global_names_ptr[self_fun]; - int argc = _code_ptr[ip + 2]; - GD_ERR_BREAK(argc < 0); - Variant **argptrs = instruction_args; GET_INSTRUCTION_ARG(dst, argc); @@ -1993,8 +2098,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } if (result.get_type() != Variant::SIGNAL) { + // Not async, return immediately using the target from OPCODE_AWAIT_RESUME. + GET_VARIANT_PTR(target, 3); + *target = result; ip += 4; // Skip OPCODE_AWAIT_RESUME and its data. - // The stack pointer should be the same, so we don't need to set a return value. is_signal = false; } else { sig = result; @@ -2034,7 +2141,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a retvalue = gdfs; - Error err = sig.connect(Callable(gdfs.ptr(), "_signal_callback"), varray(gdfs), Object::CONNECT_ONESHOT); + Error err = sig.connect(callable_bind(Callable(gdfs.ptr(), "_signal_callback"), retvalue), Object::CONNECT_ONESHOT); if (err != OK) { err_text = "Error connecting to signal: " + sig.get_name() + " during await."; OPCODE_BREAK; @@ -3028,6 +3135,18 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_STORE_GLOBAL) { + CHECK_SPACE(3); + int global_idx = _code_ptr[ip + 2]; + GD_ERR_BREAK(global_idx < 0 || global_idx >= GDScriptLanguage::get_singleton()->get_global_array_size()); + + GET_INSTRUCTION_ARG(dst, 0); + *dst = GDScriptLanguage::get_singleton()->get_global_array()[global_idx]; + + ip += 3; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_STORE_NAMED_GLOBAL) { CHECK_SPACE(3); int globalname_idx = _code_ptr[ip + 2]; @@ -3062,10 +3181,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE_TYPE_ADJUST(VECTOR3I, Vector3i); OPCODE_TYPE_ADJUST(TRANSFORM2D, Transform2D); OPCODE_TYPE_ADJUST(PLANE, Plane); - OPCODE_TYPE_ADJUST(QUAT, Quat); + OPCODE_TYPE_ADJUST(QUATERNION, Quaternion); OPCODE_TYPE_ADJUST(AABB, AABB); OPCODE_TYPE_ADJUST(BASIS, Basis); - OPCODE_TYPE_ADJUST(TRANSFORM, Transform); + OPCODE_TYPE_ADJUST(TRANSFORM, Transform3D); OPCODE_TYPE_ADJUST(COLOR, Color); OPCODE_TYPE_ADJUST(STRING_NAME, StringName); OPCODE_TYPE_ADJUST(NODE_PATH, NodePath); @@ -3190,13 +3309,13 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } int err_line = line; if (err_text == "") { - err_text = "Internal Script Error! - opcode #" + itos(last_opcode) + " (report please)."; + err_text = "Internal script error! Opcode: " + itos(last_opcode) + " (please report)."; } if (!GDScriptLanguage::get_singleton()->debug_break(err_text, false)) { // debugger break did not happen - _err_print_error(err_func.utf8().get_data(), err_file.utf8().get_data(), err_line, err_text.utf8().get_data(), ERR_HANDLER_SCRIPT); + _err_print_error(err_func.utf8().get_data(), err_file.utf8().get_data(), err_line, err_text.utf8().get_data(), false, ERR_HANDLER_SCRIPT); } #endif diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index ad41b60a4e..a351bd6dad 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -145,6 +145,13 @@ String GDScriptWarning::get_message() const { case REDUNDANT_AWAIT: { return R"("await" keyword not needed in this case, because the expression isn't a coroutine nor a signal.)"; } + case EMPTY_FILE: { + return "Empty script file."; + } + case SHADOWED_GLOBAL_IDENTIFIER: { + CHECK_SYMBOLS(3); + return vformat(R"(The %s '%s' has the same name as a %s.)", symbols[0], symbols[1], symbols[2]); + } case WARNING_MAX: break; // Can't happen, but silences warning } @@ -190,6 +197,8 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "ASSERT_ALWAYS_TRUE", "ASSERT_ALWAYS_FALSE", "REDUNDANT_AWAIT", + "EMPTY_FILE", + "SHADOWED_GLOBAL_IDENTIFIER", }; static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names."); diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index 4b295b5eb8..d05f47efe7 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -68,6 +68,8 @@ public: ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true. ASSERT_ALWAYS_FALSE, // Expression for assert argument is always false. REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine). + EMPTY_FILE, // A script file is empty. + SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable. WARNING_MAX, }; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 15236d900d..80f4721e2d 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -32,7 +32,6 @@ #include "../gdscript.h" #include "../gdscript_analyzer.h" -#include "core/io/json.h" #include "gdscript_language_protocol.h" #include "gdscript_workspace.h" @@ -40,8 +39,7 @@ void ExtendGDScriptParser::update_diagnostics() { diagnostics.clear(); const List<ParserError> &errors = get_errors(); - for (const List<ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) { - const ParserError &error = E->get(); + for (const ParserError &error : errors) { lsp::Diagnostic diagnostic; diagnostic.severity = lsp::DiagnosticSeverity::Error; diagnostic.message = error.message; @@ -62,8 +60,7 @@ void ExtendGDScriptParser::update_diagnostics() { } const List<GDScriptWarning> &warnings = get_warnings(); - for (const List<GDScriptWarning>::Element *E = warnings.front(); E; E = E->next()) { - const GDScriptWarning &warning = E->get(); + for (const GDScriptWarning &warning : warnings) { lsp::Diagnostic diagnostic; diagnostic.severity = lsp::DiagnosticSeverity::Warning; diagnostic.message = "(" + warning.get_name() + "): " + warning.get_message(); @@ -153,9 +150,9 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p } r_symbol.kind = lsp::SymbolKind::Class; r_symbol.deprecated = false; - r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_class->start_line); - r_symbol.range.start.character = LINE_NUMBER_TO_INDEX(p_class->start_column); - r_symbol.range.end.line = LINE_NUMBER_TO_INDEX(p_class->end_line); + 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.detail = "class " + r_symbol.name; bool is_root_class = &r_symbol == &class_symbol; @@ -168,7 +165,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p case ClassNode::Member::VARIABLE: { lsp::DocumentSymbol symbol; symbol.name = m.variable->identifier->name; - symbol.kind = lsp::SymbolKind::Variable; + 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); @@ -183,7 +180,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p symbol.detail += ": " + m.get_datatype().to_string(); } if (m.variable->initializer != nullptr && m.variable->initializer->is_constant) { - symbol.detail += " = " + JSON::print(m.variable->initializer->reduced_value); + symbol.detail += " = " + m.variable->initializer->reduced_value.to_json_string(); } symbol.documentation = parse_documentation(LINE_NUMBER_TO_INDEX(m.variable->start_line)); @@ -224,10 +221,10 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p } } } else { - value_text = JSON::print(default_value); + value_text = default_value.to_json_string(); } } else { - value_text = JSON::print(default_value); + value_text = default_value.to_json_string(); } if (!value_text.is_empty()) { symbol.detail += " = " + value_text; @@ -272,7 +269,7 @@ void ExtendGDScriptParser::parse_class_symbol(const GDScriptParser::ClassNode *p if (j > 0) { symbol.detail += ", "; } - symbol.detail += m.signal->parameters[i]->identifier->name; + symbol.detail += m.signal->parameters[j]->identifier->name; } symbol.detail += ")"; @@ -320,7 +317,7 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN const String uri = get_uri(); r_symbol.name = p_func->identifier->name; - r_symbol.kind = lsp::SymbolKind::Function; + r_symbol.kind = p_func->is_static ? lsp::SymbolKind::Function : lsp::SymbolKind::Method; r_symbol.detail = "func " + String(p_func->identifier->name) + "("; r_symbol.deprecated = false; r_symbol.range.start.line = LINE_NUMBER_TO_INDEX(p_func->start_line); @@ -353,8 +350,7 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN parameters += ": " + parameter->get_datatype().to_string(); } if (parameter->default_value != nullptr) { - String value = JSON::print(parameter->default_value->reduced_value); - parameters += " = " + value; + parameters += " = " + parameter->default_value->reduced_value.to_json_string(); } } r_symbol.detail += parameters + ")"; @@ -469,8 +465,8 @@ String ExtendGDScriptParser::parse_documentation(int p_line, bool p_docs_down) { } String doc; - for (List<String>::Element *E = doc_lines.front(); E; E = E->next()) { - doc += E->get() + "\n"; + for (const String &E : doc_lines) { + doc += E + "\n"; } return doc; } @@ -495,7 +491,7 @@ String ExtendGDScriptParser::get_text_for_completion(const lsp::Position &p_curs return longthing; } -String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol, bool p_func_requred) const { +String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol, bool p_func_required) const { String longthing; int len = lines.size(); for (int i = 0; i < len; i++) { @@ -517,7 +513,7 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c longthing += first_part; longthing += String::chr(0xFFFF); //not unicode, represents the cursor - if (p_func_requred) { + if (p_func_required) { longthing += "("; // tell the parser this is a function call } longthing += last_part; @@ -536,6 +532,9 @@ String ExtendGDScriptParser::get_text_for_lookup_symbol(const lsp::Position &p_c String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const { ERR_FAIL_INDEX_V(p_position.line, lines.size(), ""); String line = lines[p_position.line]; + if (line.is_empty()) { + return ""; + } ERR_FAIL_INDEX_V(p_position.character, line.size(), ""); int start_pos = p_position.character; @@ -697,7 +696,9 @@ Dictionary ExtendGDScriptParser::dump_function_api(const GDScriptParser::Functio ERR_FAIL_NULL_V(p_func, func); func["name"] = p_func->identifier->name; func["return_type"] = p_func->get_datatype().to_string(); - func["rpc_mode"] = p_func->rpc_mode; + func["rpc_mode"] = p_func->rpc_config.rpc_mode; + func["rpc_transfer_mode"] = p_func->rpc_config.transfer_mode; + func["rpc_transfer_channel"] = p_func->rpc_config.channel; Array parameters; for (int i = 0; i < p_func->parameters.size(); i++) { Dictionary arg; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index 28b9b3c82a..5d7b16765b 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -85,7 +85,7 @@ public: Error get_left_function_call(const lsp::Position &p_position, lsp::Position &r_func_pos, int &r_arg_index) const; 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_requred = false) 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_uri() const; diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index c16a7fa889..578943696e 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -31,7 +31,6 @@ #include "gdscript_language_protocol.h" #include "core/config/project_settings.h" -#include "core/io/json.h" #include "editor/doc_tools.h" #include "editor/editor_log.h" #include "editor/editor_node.h" @@ -116,7 +115,7 @@ Error GDScriptLanguageProtocol::LSPeer::send_data() { // Response sent if (res_sent >= c_res.size() - 1) { res_sent = 0; - res_queue.remove(0); + res_queue.remove_at(0); } } return OK; @@ -129,13 +128,13 @@ Error GDScriptLanguageProtocol::on_client_connected() { peer->connection = tcp_peer; clients.set(next_client_id, peer); next_client_id++; - EditorNode::get_log()->add_message("Connection Taken", EditorLog::MSG_TYPE_EDITOR); + EditorNode::get_log()->add_message("[LSP] Connection Taken", EditorLog::MSG_TYPE_EDITOR); return OK; } void GDScriptLanguageProtocol::on_client_disconnected(const int &p_client_id) { clients.erase(p_client_id); - EditorNode::get_log()->add_message("Disconnected", EditorLog::MSG_TYPE_EDITOR); + EditorNode::get_log()->add_message("[LSP] Disconnected", EditorLog::MSG_TYPE_EDITOR); } String GDScriptLanguageProtocol::process_message(const String &p_text) { @@ -194,7 +193,7 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { vformat("GDScriptLanguageProtocol: Can't initialize invalid peer '%d'.", latest_client_id)); Ref<LSPeer> peer = clients.get(latest_client_id); if (peer != nullptr) { - String msg = JSON::print(request); + String msg = Variant(request).to_json_string(); msg = format_output(msg); (*peer)->res_queue.push_back(msg.utf8()); } @@ -213,11 +212,11 @@ void GDScriptLanguageProtocol::initialized(const Variant &p_params) { lsp::GodotCapabilities capabilities; DocTools *doc = EditorHelp::get_doc_data(); - for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) { + for (const KeyValue<String, DocData::ClassDoc> &E : doc->class_list) { lsp::GodotNativeClassInfo gdclass; - gdclass.name = E->get().name; - gdclass.class_doc = &(E->get()); - if (ClassDB::ClassInfo *ptr = ClassDB::classes.getptr(StringName(E->get().name))) { + gdclass.name = E.value.name; + gdclass.class_doc = &(E.value); + if (ClassDB::ClassInfo *ptr = ClassDB::classes.getptr(StringName(E.value.name))) { gdclass.class_info = ptr; } capabilities.native_classes.push_back(gdclass); @@ -280,7 +279,24 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia ERR_FAIL_COND(peer == nullptr); Dictionary message = make_notification(p_method, p_params); - String msg = JSON::print(message); + String msg = Variant(message).to_json_string(); + msg = format_output(msg); + peer->res_queue.push_back(msg.utf8()); +} + +void GDScriptLanguageProtocol::request_client(const String &p_method, const Variant &p_params, int p_client_id) { + if (p_client_id == -1) { + ERR_FAIL_COND_MSG(latest_client_id == -1, + "GDScript LSP: Can't notify client as none was connected."); + p_client_id = latest_client_id; + } + ERR_FAIL_COND(!clients.has(p_client_id)); + Ref<LSPeer> peer = clients.get(p_client_id); + ERR_FAIL_COND(peer == nullptr); + + Dictionary message = make_request(p_method, p_params, next_server_id); + next_server_id++; + String msg = Variant(message).to_json_string(); msg = format_output(msg); peer->res_queue.push_back(msg.utf8()); } @@ -294,10 +310,10 @@ bool GDScriptLanguageProtocol::is_goto_native_symbols_enabled() const { } GDScriptLanguageProtocol::GDScriptLanguageProtocol() { - server.instance(); + server.instantiate(); singleton = this; - workspace.instance(); - text_document.instance(); + workspace.instantiate(); + text_document.instantiate(); set_scope("textDocument", text_document.ptr()); set_scope("completionItem", text_document.ptr()); set_scope("workspace", workspace.ptr()); diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h index a5c5a233b1..a4a63a23a8 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.h +++ b/modules/gdscript/language_server/gdscript_language_protocol.h @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef GDSCRIPT_PROTOCAL_SERVER_H -#define GDSCRIPT_PROTOCAL_SERVER_H +#ifndef GDSCRIPT_LANGUAGE_PROTOCOL_H +#define GDSCRIPT_LANGUAGE_PROTOCOL_H #include "core/io/stream_peer.h" #include "core/io/stream_peer_tcp.h" @@ -37,7 +37,13 @@ #include "gdscript_text_document.h" #include "gdscript_workspace.h" #include "lsp.hpp" + +#include "modules/modules_enabled.gen.h" // For jsonrpc. +#ifdef MODULE_JSONRPC_ENABLED #include "modules/jsonrpc/jsonrpc.h" +#else +#error "Can't build GDScript LSP without JSONRPC module." +#endif #define LSP_MAX_BUFFER_SIZE 4194304 #define LSP_MAX_CLIENTS 8 @@ -46,7 +52,7 @@ class GDScriptLanguageProtocol : public JSONRPC { GDCLASS(GDScriptLanguageProtocol, JSONRPC) private: - struct LSPeer : Reference { + struct LSPeer : RefCounted { Ref<StreamPeerTCP> connection; uint8_t req_buf[LSP_MAX_BUFFER_SIZE]; @@ -73,6 +79,8 @@ private: int latest_client_id = 0; int next_client_id = 0; + int next_server_id = 0; + Ref<GDScriptTextDocument> text_document; Ref<GDScriptWorkspace> workspace; @@ -101,6 +109,7 @@ public: void stop(); void notify_client(const String &p_method, const Variant &p_params = Variant(), int p_client_id = -1); + void request_client(const String &p_method, const Variant &p_params = Variant(), int p_client_id = -1); bool is_smart_resolve_enabled() const; bool is_goto_native_symbols_enabled() const; @@ -108,4 +117,4 @@ public: GDScriptLanguageProtocol(); }; -#endif +#endif // GDSCRIPT_LANGUAGE_PROTOCOL_H diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp index 340a7b9343..41a2f9e4ad 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -30,12 +30,13 @@ #include "gdscript_language_server.h" -#include "core/os/file_access.h" +#include "core/io/file_access.h" #include "core/os/os.h" #include "editor/editor_log.h" #include "editor/editor_node.h" GDScriptLanguageServer::GDScriptLanguageServer() { + _EDITOR_DEF("network/language_server/remote_host", host); _EDITOR_DEF("network/language_server/remote_port", port); _EDITOR_DEF("network/language_server/enable_smart_resolve", true); _EDITOR_DEF("network/language_server/show_native_symbols_in_editor", false); @@ -56,9 +57,10 @@ void GDScriptLanguageServer::_notification(int p_what) { } } break; case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + String host = String(_EDITOR_GET("network/language_server/remote_host")); int port = (int)_EDITOR_GET("network/language_server/remote_port"); bool use_thread = (bool)_EDITOR_GET("network/language_server/use_thread"); - if (port != this->port || use_thread != this->use_thread) { + if (host != this->host || port != this->port || use_thread != this->use_thread) { this->stop(); this->start(); } @@ -76,9 +78,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"); use_thread = (bool)_EDITOR_GET("network/language_server/use_thread"); - if (protocol.start(port, IPAddress("127.0.0.1")) == OK) { + if (protocol.start(port, IPAddress(host)) == OK) { EditorNode::get_log()->add_message("--- GDScript language server started ---", EditorLog::MSG_TYPE_EDITOR); if (use_thread) { thread_running = true; @@ -101,7 +104,7 @@ void GDScriptLanguageServer::stop() { } void register_lsp_types() { - ClassDB::register_class<GDScriptLanguageProtocol>(); - ClassDB::register_class<GDScriptTextDocument>(); - ClassDB::register_class<GDScriptWorkspace>(); + GDREGISTER_CLASS(GDScriptLanguageProtocol); + GDREGISTER_CLASS(GDScriptTextDocument); + GDREGISTER_CLASS(GDScriptWorkspace); } diff --git a/modules/gdscript/language_server/gdscript_language_server.h b/modules/gdscript/language_server/gdscript_language_server.h index 29c5ddd70e..f1413f0133 100644 --- a/modules/gdscript/language_server/gdscript_language_server.h +++ b/modules/gdscript/language_server/gdscript_language_server.h @@ -44,15 +44,14 @@ class GDScriptLanguageServer : public EditorPlugin { bool thread_running = false; bool started = false; bool use_thread = false; + String host = "127.0.0.1"; int port = 6008; static void thread_main(void *p_userdata); private: void _notification(int p_what); - void _iteration(); public: - Error parse_script_file(const String &p_path); 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 030633274c..92ce71f395 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -40,11 +40,14 @@ void GDScriptTextDocument::_bind_methods() { ClassDB::bind_method(D_METHOD("didOpen"), &GDScriptTextDocument::didOpen); + ClassDB::bind_method(D_METHOD("didClose"), &GDScriptTextDocument::didClose); ClassDB::bind_method(D_METHOD("didChange"), &GDScriptTextDocument::didChange); + ClassDB::bind_method(D_METHOD("didSave"), &GDScriptTextDocument::didSave); ClassDB::bind_method(D_METHOD("nativeSymbol"), &GDScriptTextDocument::nativeSymbol); ClassDB::bind_method(D_METHOD("documentSymbol"), &GDScriptTextDocument::documentSymbol); 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("foldingRange"), &GDScriptTextDocument::foldingRange); ClassDB::bind_method(D_METHOD("codeLens"), &GDScriptTextDocument::codeLens); ClassDB::bind_method(D_METHOD("documentLink"), &GDScriptTextDocument::documentLink); @@ -61,6 +64,11 @@ void GDScriptTextDocument::didOpen(const Variant &p_param) { sync_script_content(doc.uri, doc.text); } +void GDScriptTextDocument::didClose(const Variant &p_param) { + // Left empty on purpose. Godot does nothing special on closing a document, + // but it satisfies LSP clients that require didClose be implemented. +} + void GDScriptTextDocument::didChange(const Variant &p_param) { lsp::TextDocumentItem doc = load_document_item(p_param); Dictionary dict = p_param; @@ -73,6 +81,20 @@ void GDScriptTextDocument::didChange(const Variant &p_param) { sync_script_content(doc.uri, doc.text); } +void GDScriptTextDocument::didSave(const Variant &p_param) { + lsp::TextDocumentItem doc = load_document_item(p_param); + Dictionary dict = p_param; + String text = dict["text"]; + + sync_script_content(doc.uri, text); + + /*String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(doc.uri); + + Ref<GDScript> script = ResourceLoader::load(path); + script->load_source_code(path); + script->reload(true);*/ +} + lsp::TextDocumentItem GDScriptTextDocument::load_document_item(const Variant &p_param) { lsp::TextDocumentItem doc; Dictionary params = p_param; @@ -151,8 +173,7 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { int i = 0; arr.resize(options.size()); - for (const List<ScriptCodeCompletionOption>::Element *E = options.front(); E; E = E->next()) { - const ScriptCodeCompletionOption &option = E->get(); + for (const ScriptCodeCompletionOption &option : options) { lsp::CompletionItem item; item.label = option.display; item.data = request_data; @@ -196,8 +217,8 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { arr = native_member_completions.duplicate(); - for (Map<String, ExtendGDScriptParser *>::Element *E = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.front(); E; E = E->next()) { - ExtendGDScriptParser *script = E->get(); + for (KeyValue<String, ExtendGDScriptParser *> &E : GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts) { + ExtendGDScriptParser *script = E.value; const Array &items = script->get_member_completions(); const int start_size = arr.size(); @@ -210,6 +231,14 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { return arr; } +Dictionary GDScriptTextDocument::rename(const Dictionary &p_params) { + lsp::TextDocumentPositionParams params; + params.load(p_params); + String new_name = p_params["newName"]; + + return GDScriptLanguageProtocol::get_singleton()->get_workspace()->rename(params, new_name); +} + Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { lsp::CompletionItem item; item.load(p_params); @@ -262,8 +291,8 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { } } else if (item.kind == lsp::CompletionItemKind::Event) { if (params.context.triggerKind == lsp::CompletionTriggerKind::TriggerCharacter && (params.context.triggerCharacter == "(")) { - const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; - item.insertText = quote_style + item.label + quote_style; + const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\""; + item.insertText = item.label.quote(quote_style); } } @@ -288,8 +317,8 @@ Array GDScriptTextDocument::documentLink(const Dictionary &p_params) { List<lsp::DocumentLink> links; GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_document_links(params.textDocument.uri, links); - for (const List<lsp::DocumentLink>::Element *E = links.front(); E; E = E->next()) { - ret.push_back(E->get().to_json()); + for (const lsp::DocumentLink &E : links) { + ret.push_back(E.to_json()); } return ret; } @@ -316,8 +345,8 @@ Variant GDScriptTextDocument::hover(const Dictionary &p_params) { Array contents; List<const lsp::DocumentSymbol *> list; GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(params, list); - for (List<const lsp::DocumentSymbol *>::Element *E = list.front(); E; E = E->next()) { - if (const lsp::DocumentSymbol *s = E->get()) { + for (const lsp::DocumentSymbol *&E : list) { + if (const lsp::DocumentSymbol *s = E) { contents.push_back(s->render().value); } } @@ -367,7 +396,7 @@ Variant GDScriptTextDocument::declaration(const Dictionary &p_params) { id = "class_global:" + symbol->native_class + ":" + symbol->name; break; } - call_deferred("show_native_symbol_in_editor", id); + call_deferred(SNAME("show_native_symbol_in_editor"), id); } else { notify_client_show_symbol(symbol); } @@ -399,12 +428,23 @@ GDScriptTextDocument::~GDScriptTextDocument() { void GDScriptTextDocument::sync_script_content(const String &p_path, const String &p_content) { String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_path); + if (!path.begins_with("res://")) { + return; + } GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content); + EditorFileSystem::get_singleton()->update_file(path); + Error error; + Ref<GDScript> script = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &error); + if (error == OK) { + if (script->load_source_code(path) == OK) { + script->reload(true); + } + } } void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) { - ScriptEditor::get_singleton()->call_deferred("_help_class_goto", p_symbol_id); + ScriptEditor::get_singleton()->call_deferred(SNAME("_help_class_goto"), p_symbol_id); DisplayServer::get_singleton()->window_move_to_foreground(); } @@ -424,8 +464,8 @@ Array GDScriptTextDocument::find_symbols(const lsp::TextDocumentPositionParams & } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { List<const lsp::DocumentSymbol *> list; GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(p_location, list); - for (List<const lsp::DocumentSymbol *>::Element *E = list.front(); E; E = E->next()) { - if (const lsp::DocumentSymbol *s = E->get()) { + for (const lsp::DocumentSymbol *&E : list) { + if (const lsp::DocumentSymbol *s = E) { if (!s->uri.is_empty()) { lsp::Location location; location.uri = s->uri; diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h index 792e601bc1..9021c84a3f 100644 --- a/modules/gdscript/language_server/gdscript_text_document.h +++ b/modules/gdscript/language_server/gdscript_text_document.h @@ -31,19 +31,21 @@ #ifndef GDSCRIPT_TEXT_DOCUMENT_H #define GDSCRIPT_TEXT_DOCUMENT_H -#include "core/object/reference.h" -#include "core/os/file_access.h" +#include "core/io/file_access.h" +#include "core/object/ref_counted.h" #include "lsp.hpp" -class GDScriptTextDocument : public Reference { - GDCLASS(GDScriptTextDocument, Reference) +class GDScriptTextDocument : public RefCounted { + GDCLASS(GDScriptTextDocument, RefCounted) protected: static void _bind_methods(); FileAccess *file_checker; void didOpen(const Variant &p_param); + void didClose(const Variant &p_param); void didChange(const Variant &p_param); + void didSave(const Variant &p_param); void sync_script_content(const String &p_path, const String &p_content); void show_native_symbol_in_editor(const String &p_symbol_id); @@ -60,6 +62,7 @@ public: Array documentSymbol(const Dictionary &p_params); Array completion(const Dictionary &p_params); Dictionary resolve(const Dictionary &p_params); + Dictionary rename(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 9b7b2b36b4..932bfb2caa 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -42,6 +42,7 @@ #include "scene/resources/packed_scene.h" void GDScriptWorkspace::_bind_methods() { + ClassDB::bind_method(D_METHOD("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); @@ -52,6 +53,54 @@ void GDScriptWorkspace::_bind_methods() { ClassDB::bind_method(D_METHOD("generate_script_api", "path"), &GDScriptWorkspace::generate_script_api); } +void GDScriptWorkspace::apply_new_signal(Object *obj, String function, PackedStringArray args) { + String function_signature = "func " + function; + Ref<Script> script = obj->get_script(); + + String source = script->get_source_code(); + + if (source.find(function_signature) != -1) { + return; + } + + int first_class = source.find("\nclass "); + int start_line = 0; + if (first_class != -1) { + start_line = source.substr(0, first_class).split("\n").size(); + } else { + start_line = source.split("\n").size(); + } + + String function_body = "\n\n" + function_signature + "("; + for (int i = 0; i < args.size(); ++i) { + function_body += args[i]; + if (i < args.size() - 1) { + function_body += ", "; + } + } + function_body += ")"; + if (EditorSettings::get_singleton()->get_setting("text_editor/completion/add_type_hints")) { + function_body += " -> void"; + } + function_body += ":\n\tpass # Replace with function body.\n"; + + lsp::TextEdit text_edit; + + if (first_class != -1) { + function_body += "\n\n"; + } + text_edit.range.end.line = text_edit.range.start.line = start_line; + + text_edit.newText = function_body; + + String uri = get_file_uri(script->get_path()); + + lsp::ApplyWorkspaceEditParams params; + params.edit.add_edit(uri, text_edit); + + GDScriptLanguageProtocol::get_singleton()->request_client("workspace/applyEdit", params.to_json()); +} + void GDScriptWorkspace::did_delete_files(const Dictionary &p_params) { Array files = p_params["files"]; for (int i = 0; i < files.size(); ++i) { @@ -116,11 +165,40 @@ const lsp::DocumentSymbol *GDScriptWorkspace::get_script_symbol(const String &p_ return nullptr; } +const lsp::DocumentSymbol *GDScriptWorkspace::get_parameter_symbol(const lsp::DocumentSymbol *p_parent, const String &symbol_identifier) { + for (int i = 0; i < p_parent->children.size(); ++i) { + const lsp::DocumentSymbol *parameter_symbol = &p_parent->children[i]; + if (!parameter_symbol->detail.is_empty() && parameter_symbol->name == symbol_identifier) { + return parameter_symbol; + } + } + + 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(); + + for (int i = 0; i < class_symbol->children.size(); ++i) { + if (class_symbol->children[i].kind == lsp::SymbolKind::Function || class_symbol->children[i].kind == lsp::SymbolKind::Class) { + const lsp::DocumentSymbol *function_symbol = &class_symbol->children[i]; + + 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; + } + } + } + } + + return nullptr; +} + void GDScriptWorkspace::reload_all_workspace_scripts() { List<String> paths; list_script_files("res://", paths); - for (List<String>::Element *E = paths.front(); E; E = E->next()) { - const String &path = E->get(); + for (const String &path : paths) { Error err; String content = FileAccess::get_file_as_string(path, &err); ERR_CONTINUE(err != OK); @@ -183,12 +261,14 @@ Array GDScriptWorkspace::symbol(const Dictionary &p_params) { String query = p_params["query"]; Array arr; if (!query.is_empty()) { - for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) { + for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) { Vector<lsp::DocumentedSymbolInformation> script_symbols; - E->get()->get_symbols().symbol_tree_as_list(E->key(), 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_ofi(script_symbols[i].name)) { - arr.push_back(script_symbols[i].to_json()); + lsp::DocumentedSymbolInformation symbol = script_symbols[i]; + symbol.location.uri = get_file_uri(symbol.location.uri); + arr.push_back(symbol.to_json()); } } } @@ -202,10 +282,10 @@ Error GDScriptWorkspace::initialize() { } DocTools *doc = EditorHelp::get_doc_data(); - for (Map<String, DocData::ClassDoc>::Element *E = doc->class_list.front(); E; E = E->next()) { - const DocData::ClassDoc &class_data = E->value(); + for (const KeyValue<String, DocData::ClassDoc> &E : doc->class_list) { + const DocData::ClassDoc &class_data = E.value; lsp::DocumentSymbol class_symbol; - String class_name = E->key(); + String class_name = E.key; class_symbol.name = class_name; class_symbol.native_class = class_name; class_symbol.kind = lsp::SymbolKind::Class; @@ -230,18 +310,13 @@ Error GDScriptWorkspace::initialize() { class_symbol.children.push_back(symbol); } - Vector<DocData::PropertyDoc> properties; - properties.append_array(class_data.properties); - const int theme_prop_start_idx = properties.size(); - properties.append_array(class_data.theme_properties); - for (int i = 0; i < class_data.properties.size(); i++) { const DocData::PropertyDoc &data = class_data.properties[i]; lsp::DocumentSymbol symbol; symbol.name = data.name; symbol.native_class = class_name; symbol.kind = lsp::SymbolKind::Property; - symbol.detail = String(i >= theme_prop_start_idx ? "<Theme> var" : "var") + " " + class_name + "." + data.name; + symbol.detail = "var " + class_name + "." + data.name; if (data.enumeration.length()) { symbol.detail += ": " + data.enumeration; } else { @@ -251,8 +326,21 @@ Error GDScriptWorkspace::initialize() { class_symbol.children.push_back(symbol); } + for (int i = 0; i < class_data.theme_properties.size(); i++) { + const DocData::ThemeItemDoc &data = class_data.theme_properties[i]; + lsp::DocumentSymbol symbol; + symbol.name = data.name; + symbol.native_class = class_name; + symbol.kind = lsp::SymbolKind::Property; + symbol.detail = "<Theme> var " + class_name + "." + data.name + ": " + data.type; + symbol.documentation = data.description; + class_symbol.children.push_back(symbol); + } + Vector<DocData::MethodDoc> methods_signals; + methods_signals.append_array(class_data.constructors); methods_signals.append_array(class_data.methods); + methods_signals.append_array(class_data.operators); const int signal_start_idx = methods_signals.size(); methods_signals.append_array(class_data.signals); @@ -307,22 +395,25 @@ Error GDScriptWorkspace::initialize() { reload_all_workspace_scripts(); if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { - for (Map<StringName, lsp::DocumentSymbol>::Element *E = native_symbols.front(); E; E = E->next()) { + for (const KeyValue<StringName, lsp::DocumentSymbol> &E : native_symbols) { ClassMembers members; - const lsp::DocumentSymbol &class_symbol = E->get(); + const lsp::DocumentSymbol &class_symbol = E.value; for (int i = 0; i < class_symbol.children.size(); i++) { const lsp::DocumentSymbol &symbol = class_symbol.children[i]; members.set(symbol.name, &symbol); } - native_members.set(E->key(), members); + native_members.set(E.key, members); } // cache member completions - for (Map<String, ExtendGDScriptParser *>::Element *S = scripts.front(); S; S = S->next()) { - S->get()->get_member_completions(); + for (const KeyValue<String, ExtendGDScriptParser *> &S : scripts) { + S.value->get_member_completions(); } } + EditorNode *editor_node = EditorNode::get_singleton(); + editor_node->connect("script_add_function_request", callable_mp(this, &GDScriptWorkspace::apply_new_signal)); + return OK; } @@ -349,6 +440,50 @@ 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); + + lsp::WorkspaceEdit edit; + + List<String> paths; + list_script_files("res://", paths); + + const lsp::DocumentSymbol *reference_symbol = resolve_symbol(p_doc_pos); + if (reference_symbol) { + String identifier = reference_symbol->name; + + 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]; + + int character = line.find(identifier); + while (character > -1) { + lsp::TextDocumentPositionParams params; + + lsp::TextDocumentIdentifier text_doc; + text_doc.uri = get_file_uri(PE->get()); + + params.textDocument = text_doc; + params.position.line = i; + params.position.character = character; + + const lsp::DocumentSymbol *other_symbol = resolve_symbol(params); + + if (other_symbol == reference_symbol) { + edit.add_change(text_doc.uri, i, character, character + identifier.length(), new_name); + } + + character = line.find(identifier, character + 1); + } + } + } + } + + return edit.to_json(); +} + Error GDScriptWorkspace::parse_local_script(const String &p_path) { Error err; String content = FileAccess::get_file_as_string(p_path, &err); @@ -424,7 +559,7 @@ Node *GDScriptWorkspace::_get_owner_scene_node(String p_path) { RES owner_res = ResourceLoader::load(owner_path); if (Object::cast_to<PackedScene>(owner_res.ptr())) { Ref<PackedScene> owner_packed_scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*owner_res)); - owner_scene_node = owner_packed_scene->instance(); + owner_scene_node = owner_packed_scene->instantiate(); break; } } @@ -439,15 +574,38 @@ void GDScriptWorkspace::completion(const lsp::CompletionParams &p_params, List<S if (const ExtendGDScriptParser *parser = get_parse_result(path)) { Node *owner_scene_node = _get_owner_scene_node(path); + + Array stack; + Node *current = nullptr; + if (owner_scene_node != nullptr) { + stack.push_back(owner_scene_node); + + while (!stack.is_empty()) { + current = stack.pop_back(); + Ref<GDScript> script = current->get_script(); + if (script.is_valid() && script->get_path() == path) { + break; + } + for (int i = 0; i < current->get_child_count(); ++i) { + stack.push_back(current->get_child(i)); + } + } + + Ref<GDScript> script = current->get_script(); + if (!script.is_valid() || script->get_path() != path) { + current = owner_scene_node; + } + } + String code = parser->get_text_for_completion(p_params.position); - GDScriptLanguage::get_singleton()->complete_code(code, path, owner_scene_node, r_options, forced, call_hint); + GDScriptLanguage::get_singleton()->complete_code(code, path, current, r_options, forced, call_hint); if (owner_scene_node) { memdelete(owner_scene_node); } } } -const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_requred) { +const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name, bool p_func_required) { const lsp::DocumentSymbol *symbol = nullptr; String path = get_file_path(p_doc_pos.textDocument.uri); @@ -472,15 +630,24 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu } else { ScriptLanguage::LookupResult ret; - if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_requred), symbol_identifier, path, nullptr, ret)) { + if (symbol_identifier == "new" && parser->get_lines()[p_doc_pos.position.line].replace(" ", "").replace("\t", "").find("new(") > -1) { + symbol_identifier = "_init"; + } + if (OK == GDScriptLanguage::get_singleton()->lookup_code(parser->get_text_for_lookup_symbol(pos, symbol_identifier, p_func_required), symbol_identifier, path, nullptr, ret)) { if (ret.type == ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION) { String target_script_path = path; if (!ret.script.is_null()) { target_script_path = ret.script->get_path(); + } else if (!ret.class_path.is_empty()) { + target_script_path = ret.class_path; } 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)); + + if (symbol && symbol->kind == lsp::SymbolKind::Function && symbol->name != symbol_identifier) { + symbol = get_parameter_symbol(symbol, symbol_identifier); + } } } else { @@ -492,6 +659,10 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu } } else { symbol = parser->get_member_symbol(symbol_identifier); + + if (!symbol) { + symbol = get_local_symbol(parser, symbol_identifier); + } } } } @@ -516,8 +687,8 @@ void GDScriptWorkspace::resolve_related_symbols(const lsp::TextDocumentPositionP class_ptr = native_members.next(class_ptr); } - for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) { - const ExtendGDScriptParser *script = E->get(); + for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) { + const ExtendGDScriptParser *script = E.value; const ClassMembers &members = script->get_members(); if (const lsp::DocumentSymbol *const *symbol = members.getptr(symbol_identifier)) { r_list.push_back(*symbol); @@ -557,8 +728,8 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_native_symbol(const lsp::N void GDScriptWorkspace::resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list) { if (const ExtendGDScriptParser *parser = get_parse_successed_script(get_file_path(p_uri))) { const List<lsp::DocumentLink> &links = parser->get_document_links(); - for (const List<lsp::DocumentLink>::Element *E = links.front(); E; E = E->next()) { - r_list.push_back(E->get()); + for (const lsp::DocumentLink &E : links) { + r_list.push_back(E); } } } @@ -585,8 +756,7 @@ Error GDScriptWorkspace::resolve_signature(const lsp::TextDocumentPositionParams GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(text_pos, symbols); } - for (List<const lsp::DocumentSymbol *>::Element *E = symbols.front(); E; E = E->next()) { - const lsp::DocumentSymbol *symbol = E->get(); + for (const lsp::DocumentSymbol *const &symbol : symbols) { if (symbol->kind == lsp::SymbolKind::Method || symbol->kind == lsp::SymbolKind::Function) { lsp::SignatureInformation signature_info; signature_info.label = symbol->detail; @@ -618,12 +788,12 @@ GDScriptWorkspace::GDScriptWorkspace() { GDScriptWorkspace::~GDScriptWorkspace() { Set<String> cached_parsers; - for (Map<String, ExtendGDScriptParser *>::Element *E = parse_results.front(); E; E = E->next()) { - cached_parsers.insert(E->key()); + for (const KeyValue<String, ExtendGDScriptParser *> &E : parse_results) { + cached_parsers.insert(E.key); } - for (Map<String, ExtendGDScriptParser *>::Element *E = scripts.front(); E; E = E->next()) { - cached_parsers.insert(E->key()); + for (const KeyValue<String, ExtendGDScriptParser *> &E : scripts) { + cached_parsers.insert(E.key); } for (Set<String>::Element *E = cached_parsers.front(); E; E = E->next()) { diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h index 27616a2989..6f5600b5cf 100644 --- a/modules/gdscript/language_server/gdscript_workspace.h +++ b/modules/gdscript/language_server/gdscript_workspace.h @@ -37,8 +37,8 @@ #include "gdscript_extend_parser.h" #include "lsp.hpp" -class GDScriptWorkspace : public Reference { - GDCLASS(GDScriptWorkspace, Reference); +class GDScriptWorkspace : public RefCounted { + GDCLASS(GDScriptWorkspace, RefCounted); private: void _get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners); @@ -52,6 +52,8 @@ 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); void reload_all_workspace_scripts(); @@ -60,6 +62,8 @@ protected: void list_script_files(const String &p_root_dir, List<String> &r_files); + void apply_new_signal(Object *obj, String function, PackedStringArray args); + public: String root; String root_uri; @@ -83,13 +87,14 @@ public: void publish_diagnostics(const String &p_path); void completion(const lsp::CompletionParams &p_params, List<ScriptCodeCompletionOption> *r_options); - const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_requred = false); + const lsp::DocumentSymbol *resolve_symbol(const lsp::TextDocumentPositionParams &p_doc_pos, const String &p_symbol_name = "", bool p_func_required = false); void resolve_related_symbols(const lsp::TextDocumentPositionParams &p_doc_pos, List<const lsp::DocumentSymbol *> &r_list); const lsp::DocumentSymbol *resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params); void resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list); Dictionary generate_script_api(const String &p_path); Error resolve_signature(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::SignatureHelp &r_signature); void did_delete_files(const Dictionary &p_params); + Dictionary rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name); GDScriptWorkspace(); ~GDScriptWorkspace(); diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp index 47bcfeaefc..b12d1f5f3b 100644 --- a/modules/gdscript/language_server/lsp.hpp +++ b/modules/gdscript/language_server/lsp.hpp @@ -255,6 +255,72 @@ struct TextEdit { }; /** + * The edits to be applied. + */ +struct WorkspaceEdit { + /** + * Holds changes to existing resources. + */ + Map<String, Vector<TextEdit>> changes; + + _FORCE_INLINE_ void add_edit(const String &uri, const TextEdit &edit) { + if (changes.has(uri)) { + changes[uri].push_back(edit); + } else { + Vector<TextEdit> edits; + edits.push_back(edit); + changes[uri] = edits; + } + } + + _FORCE_INLINE_ Dictionary to_json() const { + Dictionary dict; + + Dictionary out_changes; + for (const KeyValue<String, Vector<TextEdit>> &E : changes) { + Array edits; + for (int i = 0; i < E.value.size(); ++i) { + Dictionary text_edit; + text_edit["range"] = E.value[i].range.to_json(); + text_edit["newText"] = E.value[i].newText; + edits.push_back(text_edit); + } + out_changes[E.key] = edits; + } + dict["changes"] = out_changes; + + return dict; + } + + _FORCE_INLINE_ void add_change(const String &uri, const int &line, const int &start_character, const int &end_character, const String &new_text) { + if (Map<String, Vector<TextEdit>>::Element *E = changes.find(uri)) { + Vector<TextEdit> edit_list = E->value(); + for (int i = 0; i < edit_list.size(); ++i) { + TextEdit edit = edit_list[i]; + if (edit.range.start.character == start_character) { + return; + } + } + } + + TextEdit new_edit; + new_edit.newText = new_text; + new_edit.range.start.line = line; + new_edit.range.start.character = start_character; + new_edit.range.end.line = line; + new_edit.range.end.character = end_character; + + if (Map<String, Vector<TextEdit>>::Element *E = changes.find(uri)) { + E->value().push_back(new_edit); + } else { + Vector<TextEdit> edit_list; + edit_list.push_back(new_edit); + changes.insert(uri, edit_list); + } + } +}; + +/** * Represents a reference to a command. * Provides a title which will be used to represent a command in the UI. * Commands are identified by a string identifier. @@ -292,21 +358,21 @@ struct Command { namespace TextDocumentSyncKind { /** - * Documents should not be synced at all. - */ + * Documents should not be synced at all. + */ static const int None = 0; /** - * Documents are synced by always sending the full content - * of the document. - */ + * Documents are synced by always sending the full content + * of the document. + */ static const int Full = 1; /** - * Documents are synced by sending the full content on open. - * After that only incremental updates to the document are - * send. - */ + * Documents are synced by sending the full content on open. + * After that only incremental updates to the document are + * send. + */ static const int Incremental = 2; }; // namespace TextDocumentSyncKind @@ -486,7 +552,7 @@ struct TextDocumentSyncOptions { * If present save notifications are sent to the server. If omitted the notification should not be * sent. */ - bool save = false; + SaveOptions save; Dictionary to_json() { Dictionary dict; @@ -494,7 +560,7 @@ struct TextDocumentSyncOptions { dict["willSave"] = willSave; dict["openClose"] = openClose; dict["change"] = change; - dict["save"] = save; + dict["save"] = save.to_json(); return dict; } }; @@ -601,20 +667,20 @@ struct TextDocumentContentChangeEvent { // Use namespace instead of enumeration to follow the LSP specifications namespace DiagnosticSeverity { /** - * Reports an error. - */ + * Reports an error. + */ static const int Error = 1; /** - * Reports a warning. - */ + * Reports a warning. + */ static const int Warning = 2; /** - * Reports an information. - */ + * Reports an information. + */ static const int Information = 3; /** - * Reports a hint. - */ + * Reports a hint. + */ static const int Hint = 4; }; // namespace DiagnosticSeverity @@ -766,7 +832,7 @@ struct MarkupContent { // Use namespace instead of enumeration to follow the LSP specifications // lsp::EnumName::EnumValue is OK but lsp::EnumValue is not -// And here C++ compilers are unhappy with our enumeration name like Color, File, Reference etc. +// And here C++ compilers are unhappy with our enumeration name like Color, File, RefCounted etc. /** * The kind of a completion entry. */ @@ -788,7 +854,7 @@ static const int Keyword = 14; static const int Snippet = 15; static const int Color = 16; static const int File = 17; -static const int Reference = 18; +static const int RefCounted = 18; static const int Folder = 19; static const int EnumMember = 20; static const int Constant = 21; @@ -805,18 +871,18 @@ static const int TypeParameter = 25; */ namespace InsertTextFormat { /** - * The primary text to be inserted is treated as a plain string. - */ + * The primary text to be inserted is treated as a plain string. + */ static const int PlainText = 1; /** - * The primary text to be inserted is treated as a snippet. - * - * A snippet can define tab stops and placeholders with `$1`, `$2` - * and `${3:foo}`. `$0` defines the final tab stop, it defaults to - * the end of the snippet. Placeholders with equal identifiers are linked, - * that is typing in one will update others too. - */ + * The primary text to be inserted is treated as a snippet. + * + * A snippet can define tab stops and placeholders with `$1`, `$2` + * and `${3:foo}`. `$0` defines the final tab stop, it defaults to + * the end of the snippet. Placeholders with equal identifiers are linked, + * that is typing in one will update others too. + */ static const int Snippet = 2; }; // namespace InsertTextFormat @@ -1018,32 +1084,32 @@ struct CompletionList { * A symbol kind. */ namespace SymbolKind { -static const int File = 0; -static const int Module = 1; -static const int Namespace = 2; -static const int Package = 3; -static const int Class = 4; -static const int Method = 5; -static const int Property = 6; -static const int Field = 7; -static const int Constructor = 8; -static const int Enum = 9; -static const int Interface = 10; -static const int Function = 11; -static const int Variable = 12; -static const int Constant = 13; -static const int String = 14; -static const int Number = 15; -static const int Boolean = 16; -static const int Array = 17; -static const int Object = 18; -static const int Key = 19; -static const int Null = 20; -static const int EnumMember = 21; -static const int Struct = 22; -static const int Event = 23; -static const int Operator = 24; -static const int TypeParameter = 25; +static const int File = 1; +static const int Module = 2; +static const int Namespace = 3; +static const int Package = 4; +static const int Class = 5; +static const int Method = 6; +static const int Property = 7; +static const int Field = 8; +static const int Constructor = 9; +static const int Enum = 10; +static const int Interface = 11; +static const int Function = 12; +static const int Variable = 13; +static const int Constant = 14; +static const int String = 15; +static const int Number = 16; +static const int Boolean = 17; +static const int Array = 18; +static const int Object = 19; +static const int Key = 20; +static const int Null = 21; +static const int EnumMember = 22; +static const int Struct = 23; +static const int Event = 24; +static const int Operator = 25; +static const int TypeParameter = 26; }; // namespace SymbolKind /** @@ -1266,6 +1332,18 @@ struct DocumentSymbol { } }; +struct ApplyWorkspaceEditParams { + WorkspaceEdit edit; + + Dictionary to_json() { + Dictionary dict; + + dict["edit"] = edit.to_json(); + + return dict; + } +}; + struct NativeSymbolInspectParams { String native_class; String symbol_name; @@ -1281,16 +1359,16 @@ struct NativeSymbolInspectParams { */ namespace FoldingRangeKind { /** - * Folding range for a comment - */ + * Folding range for a comment + */ static const String Comment = "comment"; /** - * Folding range for a imports or includes - */ + * Folding range for a imports or includes + */ static const String Imports = "imports"; /** - * Folding range for a region (e.g. `#region`) - */ + * Folding range for a region (e.g. `#region`) + */ static const String Region = "region"; } // namespace FoldingRangeKind @@ -1341,20 +1419,20 @@ struct FoldingRange { */ namespace CompletionTriggerKind { /** - * Completion was triggered by typing an identifier (24x7 code - * complete), manual invocation (e.g Ctrl+Space) or via API. - */ + * Completion was triggered by typing an identifier (24x7 code + * complete), manual invocation (e.g Ctrl+Space) or via API. + */ static const int Invoked = 1; /** - * Completion was triggered by a trigger character specified by - * the `triggerCharacters` properties of the `CompletionRegistrationOptions`. - */ + * Completion was triggered by a trigger character specified by + * the `triggerCharacters` properties of the `CompletionRegistrationOptions`. + */ static const int TriggerCharacter = 2; /** - * Completion was re-triggered as the current completion list is incomplete. - */ + * Completion was re-triggered as the current completion list is incomplete. + */ static const int TriggerForIncompleteCompletions = 3; } // namespace CompletionTriggerKind @@ -1363,8 +1441,8 @@ static const int TriggerForIncompleteCompletions = 3; */ struct CompletionContext { /** - * How the completion was triggered. - */ + * How the completion was triggered. + */ int triggerKind = CompletionTriggerKind::TriggerCharacter; /** @@ -1828,7 +1906,7 @@ struct GodotNativeClassInfo { struct GodotCapabilities { /** * Native class list - */ + */ List<GodotNativeClassInfo> native_classes; Dictionary to_json() { diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index 2d2f94f5e0..c2b1981f31 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -30,10 +30,10 @@ #include "register_types.h" +#include "core/io/dir_access.h" +#include "core/io/file_access.h" #include "core/io/file_access_encrypted.h" #include "core/io/resource_loader.h" -#include "core/os/dir_access.h" -#include "core/os/file_access.h" #include "gdscript.h" #include "gdscript_analyzer.h" #include "gdscript_cache.h" @@ -92,12 +92,12 @@ public: static void _editor_init() { Ref<EditorExportGDScript> gd_export; - gd_export.instance(); + gd_export.instantiate(); EditorExport::get_singleton()->add_export_plugin(gd_export); #ifdef TOOLS_ENABLED Ref<GDScriptSyntaxHighlighter> gdscript_syntax_highlighter; - gdscript_syntax_highlighter.instance(); + gdscript_syntax_highlighter.instantiate(); ScriptEditor::get_singleton()->register_syntax_highlighter(gdscript_syntax_highlighter); #endif @@ -112,15 +112,15 @@ static void _editor_init() { #endif // TOOLS_ENABLED void register_gdscript_types() { - ClassDB::register_class<GDScript>(); + GDREGISTER_CLASS(GDScript); script_language_gd = memnew(GDScriptLanguage); ScriptServer::register_language(script_language_gd); - resource_loader_gd.instance(); + resource_loader_gd.instantiate(); ResourceLoader::add_resource_format_loader(resource_loader_gd); - resource_saver_gd.instance(); + resource_saver_gd.instantiate(); ResourceSaver::add_resource_format_saver(resource_saver_gd); gdscript_cache = memnew(GDScriptCache); @@ -128,7 +128,7 @@ void register_gdscript_types() { #ifdef TOOLS_ENABLED EditorNode::add_init_callback(_editor_init); - gdscript_translation_parser_plugin.instance(); + gdscript_translation_parser_plugin.instantiate(); EditorTranslationParser::get_singleton()->add_parser(gdscript_translation_parser_plugin, EditorTranslationParser::STANDARD); #endif // TOOLS_ENABLED diff --git a/modules/gdscript/tests/README.md b/modules/gdscript/tests/README.md new file mode 100644 index 0000000000..6e54085962 --- /dev/null +++ b/modules/gdscript/tests/README.md @@ -0,0 +1,8 @@ +# GDScript integration tests + +The `scripts/` folder contains integration tests in the form of GDScript files +and output files. + +See the +[Integration tests for GDScript documentation](https://docs.godotengine.org/en/latest/development/cpp/unit_testing.html#integration-tests-for-gdscript) +for information about creating and running GDScript integration tests. diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 76ae43e792..d2e71efee7 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -37,8 +37,8 @@ #include "core/config/project_settings.h" #include "core/core_string_names.h" +#include "core/io/dir_access.h" #include "core/io/file_access_pack.h" -#include "core/os/dir_access.h" #include "core/os/os.h" #include "core/string/string_builder.h" #include "scene/resources/packed_scene.h" @@ -48,11 +48,11 @@ namespace GDScriptTests { void init_autoloads() { - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); // First pass, add the constants so they exist before any script is loaded. - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->get(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.get(); if (info.is_singleton) { for (int i = 0; i < ScriptServer::get_language_count(); i++) { @@ -62,8 +62,8 @@ void init_autoloads() { } // Second pass, load into global constants. - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->get(); + for (OrderedHashMap<StringName, ProjectSettings::AutoloadInfo>::Element E = autoloads.front(); E; E = E.next()) { + const ProjectSettings::AutoloadInfo &info = E.get(); if (!info.is_singleton) { // Skip non-singletons since we don't have a scene tree here anyway. @@ -75,14 +75,14 @@ void init_autoloads() { Node *n = nullptr; if (res->is_class("PackedScene")) { Ref<PackedScene> ps = res; - n = ps->instance(); + n = ps->instantiate(); } else if (res->is_class("Script")) { Ref<Script> script_res = res; StringName ibt = script_res->get_instance_base_type(); bool valid_type = ClassDB::is_parent_class(ibt, "Node"); ERR_CONTINUE_MSG(!valid_type, "Script does not inherit a Node: " + info.path); - Object *obj = ClassDB::instance(ibt); + Object *obj = ClassDB::instantiate(ibt); ERR_CONTINUE_MSG(obj == nullptr, "Cannot instance script for autoload, expected 'Node' inheritance, got: " + @@ -133,13 +133,12 @@ GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_l if (do_init_languages) { init_language(p_source_dir); - - // Enable all warnings for GDScript, so we can test them. - ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true); - for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) { - String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower(); - ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/" + warning, true); - } + } + // Enable all warnings for GDScript, so we can test them. + ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true); + for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) { + String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower(); + ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/" + warning, true); } // Enable printing to show results @@ -257,6 +256,7 @@ bool GDScriptTestRunner::make_tests() { ERR_FAIL_COND_V_MSG(err != OK, false, "Could not open specified test directory."); + source_dir = dir->get_current_dir() + "/"; // Make it absolute path. return make_tests_for_dir(dir->get_current_dir()); } @@ -294,7 +294,7 @@ void GDScriptTestRunner::handle_cmdline() { String test_cmd = "--gdscript-test"; String gen_cmd = "--gdscript-generate-tests"; - for (List<String>::Element *E = cmdline_args.front(); E != nullptr; E = E->next()) { + for (List<String>::Element *E = cmdline_args.front(); E; E = E->next()) { String &cmd = E->get(); if (cmd == test_cmd || cmd == gen_cmd) { if (E->next() == nullptr) { @@ -334,7 +334,7 @@ void GDScriptTest::print_handler(void *p_this, const String &p_message, bool p_e result->output += p_message + "\n"; } -void GDScriptTest::error_handler(void *p_this, const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_explanation, ErrorHandlerType p_type) { +void GDScriptTest::error_handler(void *p_this, const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_explanation, bool p_editor_notify, ErrorHandlerType p_type) { ErrorHandlerData *data = (ErrorHandlerData *)p_this; GDScriptTest *self = data->self; TestResult *result = data->result; @@ -361,11 +361,9 @@ void GDScriptTest::error_handler(void *p_this, const char *p_function, const cha break; } - builder.append("\n>> "); - builder.append(p_function); - builder.append("\n>> "); + builder.append("\n>> on function: "); builder.append(p_function); - builder.append("\n>> "); + builder.append("()\n>> "); builder.append(String(p_file).trim_prefix(self->base_dir)); builder.append("\n>> "); builder.append(itos(p_line)); @@ -416,12 +414,13 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { TestResult result; result.status = GDTEST_OK; result.output = String(); + result.passed = false; Error err = OK; // Create script. Ref<GDScript> script; - script.instance(); + script.instantiate(); script->set_path(source_file); script->set_script_path(source_file); err = script->load_source_code(source_file); @@ -441,8 +440,8 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { result.output = get_text_for_status(result.status) + "\n"; const List<GDScriptParser::ParserError> &errors = parser.get_errors(); - for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E; E = E->next()) { - result.output += E->get().message + "\n"; // TODO: line, column? + for (const GDScriptParser::ParserError &E : errors) { + result.output += E.message + "\n"; // TODO: line, column? break; // Only the first error since the following might be cascading. } if (!p_is_generating) { @@ -460,8 +459,8 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { result.output = get_text_for_status(result.status) + "\n"; const List<GDScriptParser::ParserError> &errors = parser.get_errors(); - for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E; E = E->next()) { - result.output += E->get().message + "\n"; // TODO: line, column? + for (const GDScriptParser::ParserError &E : errors) { + result.output += E.message + "\n"; // TODO: line, column? break; // Only the first error since the following might be cascading. } if (!p_is_generating) { @@ -471,8 +470,8 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { } StringBuilder warning_string; - for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E != nullptr; E = E->next()) { - const GDScriptWarning warning = E->get(); + for (const GDScriptWarning &E : parser.get_warnings()) { + const GDScriptWarning warning = E; warning_string.append(">> WARNING"); warning_string.append("\n>> Line: "); warning_string.append(itos(warning.start_line)); @@ -497,7 +496,12 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { } return result; } - + // Script files matching this pattern are allowed to not contain a test() function. + if (source_file.match("*.notest.gd")) { + enable_stdout(); + result.passed = check_output(result.output); + return result; + } // Test running. const Map<StringName, GDScriptFunction *>::Element *test_function_element = script->get_member_functions().find(GDScriptTestRunner::test_function_name); if (test_function_element == nullptr) { @@ -511,10 +515,10 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { script->reload(); // Create object instance for test. - Object *obj = ClassDB::instance(script->get_native()->get_name()); - Ref<Reference> obj_ref; - if (obj->is_reference()) { - obj_ref = Ref<Reference>(Object::cast_to<Reference>(obj)); + Object *obj = ClassDB::instantiate(script->get_native()->get_name()); + Ref<RefCounted> obj_ref; + if (obj->is_ref_counted()) { + obj_ref = Ref<RefCounted>(Object::cast_to<RefCounted>(obj)); } obj->set_script(script); GDScriptInstance *instance = static_cast<GDScriptInstance *>(obj->get_script_instance()); diff --git a/modules/gdscript/tests/gdscript_test_runner.h b/modules/gdscript/tests/gdscript_test_runner.h index 9b2d14a371..98c57dc97c 100644 --- a/modules/gdscript/tests/gdscript_test_runner.h +++ b/modules/gdscript/tests/gdscript_test_runner.h @@ -87,7 +87,7 @@ private: public: static void print_handler(void *p_this, const String &p_message, bool p_error); - static void error_handler(void *p_this, const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_explanation, ErrorHandlerType p_type); + static void error_handler(void *p_this, const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_explanation, bool p_editor_notify, ErrorHandlerType p_type); TestResult run_test(); bool generate_output(); diff --git a/modules/gdscript/tests/gdscript_test_runner_suite.h b/modules/gdscript/tests/gdscript_test_runner_suite.h index 136907b316..cf4e61f07d 100644 --- a/modules/gdscript/tests/gdscript_test_runner_suite.h +++ b/modules/gdscript/tests/gdscript_test_runner_suite.h @@ -48,6 +48,27 @@ TEST_SUITE("[Modules][GDScript]") { } } +TEST_CASE("[Modules][GDScript] Load source code dynamically and run it") { + Ref<GDScript> gdscript = memnew(GDScript); + gdscript->set_source_code(R"( +extends RefCounted + +func _init(): + set_meta("result", 42) +)"); + // A spurious `Condition "err" is true` message is printed (despite parsing being successful and returning `OK`). + // Silence it. + ERR_PRINT_OFF; + const Error error = gdscript->reload(); + ERR_PRINT_ON; + CHECK_MESSAGE(error == OK, "The script should parse successfully."); + + // Run the script by assigning it to a reference-counted object. + Ref<RefCounted> ref_counted = memnew(RefCounted); + ref_counted->set_script(gdscript); + CHECK_MESSAGE(int(ref_counted->get_meta("result")) == 42, "The script should assign object metadata successfully."); +} + } // namespace GDScriptTests #endif // GDSCRIPT_TEST_RUNNER_SUITE_H diff --git a/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.gd b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.gd new file mode 100644 index 0000000000..9b722ea50a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.gd @@ -0,0 +1,3 @@ +func test(): + # Error here. + print(2.2 << 4) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.out b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.out new file mode 100644 index 0000000000..7dee854d1a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_left_operand.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid operands to operator <<, float and int. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.gd b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.gd new file mode 100644 index 0000000000..0a4f647f57 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.gd @@ -0,0 +1,3 @@ +func test(): + # Error here. + print(2 >> 4.4) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.out b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.out new file mode 100644 index 0000000000..1edbf47ec0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/bitwise_float_right_operand.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid operands to operator >>, int and float. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.gd new file mode 100644 index 0000000000..b84ccdce81 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.gd @@ -0,0 +1,5 @@ +class Vector2: + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.out new file mode 100644 index 0000000000..87863baf75 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/class_name_shadows_builtin_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The member "Vector2" cannot have the same name as a builtin type. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.gd new file mode 100644 index 0000000000..a7c0a29a69 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.gd @@ -0,0 +1,4 @@ +const Vector2 = 0 + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.out new file mode 100644 index 0000000000..87863baf75 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_name_shadows_builtin_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The member "Vector2" cannot have the same name as a builtin type. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.gd b/modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.gd new file mode 100644 index 0000000000..0ad2337c15 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.gd @@ -0,0 +1,5 @@ +const CONSTANT = 25 + + +func test(): + CONSTANT(123) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.out b/modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.out new file mode 100644 index 0000000000..f4051cd02c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_used_as_function.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Member "CONSTANT" is not a function. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.gd b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.gd new file mode 100644 index 0000000000..7a922cd73e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.gd @@ -0,0 +1,6 @@ +func test(): + var lua_dict = { + a = 1, + b = 2, + a = 3, # Duplicate isn't allowed. + } diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.out b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.out new file mode 100644 index 0000000000..ffdfa56645 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Key "a" was already used in this dictionary (at line 3). diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.gd b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.gd new file mode 100644 index 0000000000..933e737ac7 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.gd @@ -0,0 +1,6 @@ +func test(): + var lua_dict_with_string = { + a = 1, + b = 2, + "a" = 3, # Duplicate isn't allowed. + } diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.out b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.out new file mode 100644 index 0000000000..ffdfa56645 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_lua_with_string.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Key "a" was already used in this dictionary (at line 3). diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.gd b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.gd new file mode 100644 index 0000000000..3b8c83e9cb --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.gd @@ -0,0 +1,6 @@ +func test(): + var python_dict = { + "a": 1, + "b": 2, + "a": 3, # Duplicate isn't allowed. + } diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.out b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.out new file mode 100644 index 0000000000..ffdfa56645 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_duplicate_key_python.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Key "a" was already used in this dictionary (at line 3). diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.gd new file mode 100644 index 0000000000..cf9a0ce038 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.gd @@ -0,0 +1,7 @@ +enum Size { + # Error here. Enum values must be integers. + S = 0.0, +} + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.out new file mode 100644 index 0000000000..b315d20508 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_float_value.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Enum values must be integers. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.gd new file mode 100644 index 0000000000..930f91b389 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.gd @@ -0,0 +1,4 @@ +enum Vector2 { A, B } + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.out new file mode 100644 index 0000000000..87863baf75 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_name_shadows_builtin_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The member "Vector2" cannot have the same name as a builtin type. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.gd new file mode 100644 index 0000000000..cd9b8fabc4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.gd @@ -0,0 +1,7 @@ +enum Size { + # Error here. Enum values must be integers. + S = "hello", +} + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.out new file mode 100644 index 0000000000..b315d20508 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_string_value.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Enum values must be integers. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.gd b/modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.gd new file mode 100644 index 0000000000..4346503fc2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.gd @@ -0,0 +1,6 @@ +func function(): + pass + + +func test(): + function = 25 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.out b/modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.out new file mode 100644 index 0000000000..5275183da2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_used_as_property.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a new value to a constant. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.gd b/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.gd new file mode 100644 index 0000000000..b8c0b7a8d3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.gd @@ -0,0 +1,3 @@ +func test(): + # Error here. Array indices must be integers. + print([0, 1][true]) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.out b/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.out new file mode 100644 index 0000000000..015ad756f8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_array_index.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid index type "bool" for a base of type "Array". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.gd b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.gd new file mode 100644 index 0000000000..c159e03140 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.gd @@ -0,0 +1,2 @@ +func test(): + print(true + true) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.out b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.out new file mode 100644 index 0000000000..c1dc7c7d08 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_bool.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid operands to operator +, bool and bool. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.gd new file mode 100644 index 0000000000..6aec2e0796 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.gd @@ -0,0 +1,2 @@ +func test(): + print({"hello": "world"} + {"godot": "engine"}) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.out b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.out new file mode 100644 index 0000000000..1b4451edbe --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_dictionary.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid operands "Dictionary" and "Dictionary" for "+" operator. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.gd b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.gd new file mode 100644 index 0000000000..eb2a6a0ce7 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.gd @@ -0,0 +1,2 @@ +func test(): + print("hello" + ["world"]) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.out b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.out new file mode 100644 index 0000000000..6d44c6c1bd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_concatenation_mixed.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid operands "String" and "Array" for "+" operator. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.gd b/modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.gd new file mode 100644 index 0000000000..a7426e88da --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.gd @@ -0,0 +1,5 @@ +func test(): + var i = 12 + # Constants must be made of a constant, deterministic expression. + # A constant that depends on a variable's value is not a constant expression. + const TEST = 13 + i diff --git a/modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.out b/modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.out new file mode 100644 index 0000000000..c40830f123 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/invalid_constant.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Assigned value for constant "TEST" isn't a constant expression. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.gd b/modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.gd new file mode 100644 index 0000000000..d88c02d6ee --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.gd @@ -0,0 +1,3 @@ +func test(): + # Number separators may not be placed at the beginning of a number. + var __ = _123 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.out b/modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.out new file mode 100644 index 0000000000..cfb558bf45 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/leading_number_separator.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Identifier "_123" not declared in the current scope. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/missing_argument.gd b/modules/gdscript/tests/scripts/analyzer/errors/missing_argument.gd new file mode 100644 index 0000000000..70bdadf291 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/missing_argument.gd @@ -0,0 +1,6 @@ +func args(a, b): + print(a) + print(b) + +func test(): + args(1,) diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_argument.out b/modules/gdscript/tests/scripts/analyzer/errors/missing_argument.out index fc2a891109..fc2a891109 100644 --- a/modules/gdscript/tests/scripts/parser/errors/missing_argument.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/missing_argument.out diff --git a/modules/gdscript/tests/scripts/analyzer/errors/property_function_get_type_error.gd b/modules/gdscript/tests/scripts/analyzer/errors/property_function_get_type_error.gd new file mode 100644 index 0000000000..f1be6aaa0c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/property_function_get_type_error.gd @@ -0,0 +1,11 @@ +var _prop : int + +# Getter function has wrong return type. +var prop : String: + get = get_prop + +func get_prop(): + return _prop + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/property_function_get_type_error.out b/modules/gdscript/tests/scripts/analyzer/errors/property_function_get_type_error.out new file mode 100644 index 0000000000..29eec51ef2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/property_function_get_type_error.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Function with return type "int" cannot be used as getter for a property of type "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/property_function_set_type_error.gd b/modules/gdscript/tests/scripts/analyzer/errors/property_function_set_type_error.gd new file mode 100644 index 0000000000..dd190157a1 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/property_function_set_type_error.gd @@ -0,0 +1,11 @@ +var _prop : int + +# Setter function has wrong argument type. +var prop : String: + set = set_prop + +func set_prop(value : int): + _prop = value + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/property_function_set_type_error.out b/modules/gdscript/tests/scripts/analyzer/errors/property_function_set_type_error.out new file mode 100644 index 0000000000..7a25280d55 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/property_function_set_type_error.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Function with argument type "int" cannot be used as setter for a property of type "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/property_inline_get_type_error.gd b/modules/gdscript/tests/scripts/analyzer/errors/property_inline_get_type_error.gd new file mode 100644 index 0000000000..7f2b29222a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/property_inline_get_type_error.gd @@ -0,0 +1,9 @@ +var _prop : int + +# Inline getter returns int instead of String. +var prop : String: + get: + return _prop + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/property_inline_get_type_error.out b/modules/gdscript/tests/scripts/analyzer/errors/property_inline_get_type_error.out new file mode 100644 index 0000000000..e0adef1bf8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/property_inline_get_type_error.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot return value of type "int" because the function return type is "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/property_inline_set_type_error.gd b/modules/gdscript/tests/scripts/analyzer/errors/property_inline_set_type_error.gd new file mode 100644 index 0000000000..0ce239dbbd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/property_inline_set_type_error.gd @@ -0,0 +1,9 @@ +var _prop : int + +# Inline setter assigns String to int. +var prop : String: + set(value): + _prop = value + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/property_inline_set_type_error.out b/modules/gdscript/tests/scripts/analyzer/errors/property_inline_set_type_error.out new file mode 100644 index 0000000000..bbadf1ce27 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/property_inline_set_type_error.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a value of type "String" to a target of type "int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.gd b/modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.gd new file mode 100644 index 0000000000..059d774927 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.gd @@ -0,0 +1,4 @@ +var property = 25 + +func test(): + property() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.out b/modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.out new file mode 100644 index 0000000000..94d6c26a1a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/property_used_as_function.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Member "property" is not a function. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.gd b/modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.gd new file mode 100644 index 0000000000..91401d32fc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.gd @@ -0,0 +1,7 @@ +# See also `parser-warnings/shadowed-constant.gd`. +const TEST = 25 + + +func test(): + # Error here (trying to set a new value to a constant). + TEST = 50 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.out b/modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.out new file mode 100644 index 0000000000..5275183da2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/redefine_class_constant.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a new value to a constant. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.gd b/modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.gd new file mode 100644 index 0000000000..97f3e55e81 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.gd @@ -0,0 +1,5 @@ +func test(): + const TEST = 25 + + # Error here (can't assign a new value to a constant). + TEST = 50 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.out b/modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.out new file mode 100644 index 0000000000..5275183da2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/redefine_local_constant.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a new value to a constant. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/setter_parameter_uses_property_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/setter_parameter_uses_property_type.gd new file mode 100644 index 0000000000..3bbee5f5f7 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/setter_parameter_uses_property_type.gd @@ -0,0 +1,8 @@ +var with_setter := 0: + set(val): + var x: String = val + with_setter = val + +func test(): + with_setter = 1 + print(with_setter) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/setter_parameter_uses_property_type.out b/modules/gdscript/tests/scripts/analyzer/errors/setter_parameter_uses_property_type.out new file mode 100644 index 0000000000..9eb2a42ccd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/setter_parameter_uses_property_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Value of type "int" cannot be assigned to a variable of type "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.gd b/modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.gd new file mode 100644 index 0000000000..722a8fcdb7 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.gd @@ -0,0 +1,11 @@ +# `class` extends RefCounted by default. +class Say: + func say(): + super() + print("say something") + + +func test(): + # RefCounted doesn't have a `say()` method, so the `super()` call in the method + # definition will cause a run-time error. + Say.new().say() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.out b/modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.out new file mode 100644 index 0000000000..e3dbf81850 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/super_nonexistent_base_method.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Function "say()" not found in base RefCounted. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.gd new file mode 100644 index 0000000000..7cba29884c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.gd @@ -0,0 +1,4 @@ +var Vector2 + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.out new file mode 100644 index 0000000000..87863baf75 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/variable_name_shadows_builtin_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The member "Vector2" cannot have the same name as a builtin type. diff --git a/modules/gdscript/tests/scripts/analyzer/features/as.gd b/modules/gdscript/tests/scripts/analyzer/features/as.gd new file mode 100644 index 0000000000..13a36147c0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/as.gd @@ -0,0 +1,16 @@ +func test(): + var some_bool = 5 as bool + var some_int = 5 as int + var some_float = 5 as float + print(typeof(some_bool)) + print(typeof(some_int)) + print(typeof(some_float)) + + print() + + var some_bool_typed := 5 as bool + var some_int_typed := 5 as int + var some_float_typed := 5 as float + print(typeof(some_bool_typed)) + print(typeof(some_int_typed)) + print(typeof(some_float_typed)) diff --git a/modules/gdscript/tests/scripts/analyzer/features/as.out b/modules/gdscript/tests/scripts/analyzer/features/as.out new file mode 100644 index 0000000000..bcf84aa6f6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/as.out @@ -0,0 +1,8 @@ +GDTEST_OK +1 +2 +3 + +1 +2 +3 diff --git a/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.gd b/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.gd new file mode 100644 index 0000000000..f64dce26c9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.gd @@ -0,0 +1,9 @@ +func inferred_parameter(param = null): + if param == null: + param = Node.new() + param.name = "Ok" + print(param.name) + param.free() + +func test(): + inferred_parameter() diff --git a/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.out b/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.out new file mode 100644 index 0000000000..481016138a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.out @@ -0,0 +1,6 @@ +GDTEST_OK +>> WARNING +>> Line: 6 +>> UNSAFE_METHOD_ACCESS +>> The method 'free' is not present on the inferred type 'Variant' (but may be present on a subtype). +Ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.gd b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.gd new file mode 100644 index 0000000000..d21d8bce96 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.gd @@ -0,0 +1,9 @@ +extends Node + +func test(): + set_name("TestNodeName") + if get_name() == &"TestNodeName": + print("Name is equal") + else: + print("Name is not equal") + print(get_name() is StringName) diff --git a/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out new file mode 100644 index 0000000000..2c8cc9c03f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/call_self_get_name.out @@ -0,0 +1,3 @@ +GDTEST_OK +Name is equal +true diff --git a/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.gd b/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.gd new file mode 100644 index 0000000000..ac66b78220 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.gd @@ -0,0 +1,3 @@ +func test(): + print(Color.html_is_valid("00ffff")) + print("OK") diff --git a/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out b/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out new file mode 100644 index 0000000000..ad6beeb646 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/call_static_builtin_function.out @@ -0,0 +1,3 @@ +GDTEST_OK +true +OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/class_from_parent.gd b/modules/gdscript/tests/scripts/analyzer/features/class_from_parent.gd new file mode 100644 index 0000000000..30e7deb05a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/class_from_parent.gd @@ -0,0 +1,19 @@ +class A: + var x = 3 + +class B: + var x = 4 + +class C: + var x = 5 + +class Test: + var a = A.new() + var b: B = B.new() + var c := C.new() + +func test(): + var test_instance := Test.new() + prints(test_instance.a.x) + prints(test_instance.b.x) + prints(test_instance.c.x) diff --git a/modules/gdscript/tests/scripts/analyzer/features/class_from_parent.out b/modules/gdscript/tests/scripts/analyzer/features/class_from_parent.out new file mode 100644 index 0000000000..a078e62cc7 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/class_from_parent.out @@ -0,0 +1,4 @@ +GDTEST_OK +3 +4 +5 diff --git a/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.gd b/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.gd new file mode 100644 index 0000000000..630b20c282 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.gd @@ -0,0 +1,11 @@ +# https://github.com/godotengine/godot/issues/43503 + +var test_var = null + + +func test(): + print(test_var.x) + + +func _init(): + test_var = Vector3() diff --git a/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out b/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out new file mode 100644 index 0000000000..94e2ec2af8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/class_inference_is_weak.out @@ -0,0 +1,2 @@ +GDTEST_OK +0 diff --git a/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.gd b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.gd new file mode 100644 index 0000000000..135b6c3d85 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.gd @@ -0,0 +1,16 @@ +extends Node + +const NO_TYPE_CONST = 0 +const TYPE_CONST: int = 1 +const GUESS_TYPE_CONST := 2 + +class Test: + var a = NO_TYPE_CONST + var b = TYPE_CONST + var c = GUESS_TYPE_CONST + +func test(): + var test_instance = Test.new() + prints("a", test_instance.a, test_instance.a == NO_TYPE_CONST) + prints("b", test_instance.b, test_instance.b == TYPE_CONST) + prints("c", test_instance.c, test_instance.c == GUESS_TYPE_CONST) diff --git a/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.out b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.out new file mode 100644 index 0000000000..a96bb84246 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.out @@ -0,0 +1,4 @@ +GDTEST_OK +a 0 true +b 1 true +c 2 true diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd new file mode 100644 index 0000000000..5f57c5b8c2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd @@ -0,0 +1,14 @@ +extends Node + +enum Named { VALUE_A, VALUE_B, VALUE_C = 42 } + +class Test: + var a = Named.VALUE_A + var b = Named.VALUE_B + var c = Named.VALUE_C + +func test(): + var test_instance = Test.new() + prints("a", test_instance.a, test_instance.a == Named.VALUE_A) + prints("b", test_instance.b, test_instance.b == Named.VALUE_B) + prints("c", test_instance.c, test_instance.c == Named.VALUE_C) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out new file mode 100644 index 0000000000..c160839da3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out @@ -0,0 +1,4 @@ +GDTEST_OK +a 0 true +b 1 true +c 42 true diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.gd new file mode 100644 index 0000000000..26edce353d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.gd @@ -0,0 +1,14 @@ +extends Node + +enum { VALUE_A, VALUE_B, VALUE_C = 42 } + +class Test: + var a = VALUE_A + var b = VALUE_B + var c = VALUE_C + +func test(): + var test_instance = Test.new() + prints("a", test_instance.a, test_instance.a == VALUE_A) + prints("b", test_instance.b, test_instance.b == VALUE_B) + prints("c", test_instance.c, test_instance.c == VALUE_C) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.out b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.out new file mode 100644 index 0000000000..c160839da3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.out @@ -0,0 +1,4 @@ +GDTEST_OK +a 0 true +b 1 true +c 42 true diff --git a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.gd b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.gd new file mode 100644 index 0000000000..fb0ace6a90 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.gd @@ -0,0 +1,5 @@ +func test(): + pass + +func something(): + return "OK" diff --git a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.out b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_class_as_return_type.gd b/modules/gdscript/tests/scripts/analyzer/features/inner_class_as_return_type.gd new file mode 100644 index 0000000000..4f4b7a4897 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/inner_class_as_return_type.gd @@ -0,0 +1,11 @@ +class InnerClass: + var val := "OK" + static func create_instance() -> InnerClass: + return new() + +func create_inner_instance() -> InnerClass: + return InnerClass.create_instance() + +func test(): + var instance = create_inner_instance() + print(instance.val) diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_class_as_return_type.out b/modules/gdscript/tests/scripts/analyzer/features/inner_class_as_return_type.out new file mode 100644 index 0000000000..1ccb591560 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/inner_class_as_return_type.out @@ -0,0 +1,2 @@ +GDTEST_OK +OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/local_inference_is_weak.gd b/modules/gdscript/tests/scripts/analyzer/features/local_inference_is_weak.gd new file mode 100644 index 0000000000..b3784dffa3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/local_inference_is_weak.gd @@ -0,0 +1,14 @@ +# https://github.com/godotengine/godot/issues/41064 +var x = true + +func test(): + var int_var: int = 0 + var dyn_var = 2 + + if x: + dyn_var = 5 + else: + dyn_var = Node.new() + + int_var = dyn_var + print(int_var) diff --git a/modules/gdscript/tests/scripts/analyzer/features/local_inference_is_weak.out b/modules/gdscript/tests/scripts/analyzer/features/local_inference_is_weak.out new file mode 100644 index 0000000000..952029f665 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/local_inference_is_weak.out @@ -0,0 +1,2 @@ +GDTEST_OK +5 diff --git a/modules/gdscript/tests/scripts/analyzer/features/property_functions.gd b/modules/gdscript/tests/scripts/analyzer/features/property_functions.gd new file mode 100644 index 0000000000..1706087f82 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/property_functions.gd @@ -0,0 +1,16 @@ +var _prop = 1 +var prop: + get = get_prop, set = set_prop + +func get_prop(): + return _prop + +func set_prop(value): + _prop = value + +func test(): + print(prop) + + prop = 2 + + print(prop) diff --git a/modules/gdscript/tests/scripts/analyzer/features/property_functions.out b/modules/gdscript/tests/scripts/analyzer/features/property_functions.out new file mode 100644 index 0000000000..f1253ca57e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/property_functions.out @@ -0,0 +1,3 @@ +GDTEST_OK +1 +2 diff --git a/modules/gdscript/tests/scripts/analyzer/features/property_inline.gd b/modules/gdscript/tests/scripts/analyzer/features/property_inline.gd new file mode 100644 index 0000000000..23eb011b23 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/property_inline.gd @@ -0,0 +1,46 @@ +# Untyped inline property +var prop1: + get: + return prop1 + set(value): + prop1 = value + +# Typed inline property +var prop2 : int: + get: + return prop2 + set(value): + prop2 = value + +# Typed inline property with default value +var prop3 : int = 1: + get: + return prop3 + set(value): + prop3 = value + +# Typed inline property with backing variable +var _prop4 : int = 2 +var prop4: int: + get: + return _prop4 + set(value): + _prop4 = value + +func test(): + print(prop1) + print(prop2) + print(prop3) + print(prop4) + + print() + + prop1 = 1 + prop2 = 2 + prop3 = 3 + prop4 = 4 + + print(prop1) + print(prop2) + print(prop3) + print(prop4) diff --git a/modules/gdscript/tests/scripts/analyzer/features/property_inline.out b/modules/gdscript/tests/scripts/analyzer/features/property_inline.out new file mode 100644 index 0000000000..5482592e90 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/property_inline.out @@ -0,0 +1,10 @@ +GDTEST_OK +null +0 +1 +2 + +1 +2 +3 +4 diff --git a/modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.gd b/modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.gd new file mode 100644 index 0000000000..569f95850f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.gd @@ -0,0 +1,2 @@ +func test(): + print(Color.html_is_valid("00ffff")) diff --git a/modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.out b/modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.out new file mode 100644 index 0000000000..55482c2b52 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/static_method_builtin_type.out @@ -0,0 +1,2 @@ +GDTEST_OK +true diff --git a/modules/gdscript/tests/scripts/analyzer/features/subscript_self.gd b/modules/gdscript/tests/scripts/analyzer/features/subscript_self.gd new file mode 100644 index 0000000000..f9a8b23b92 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/subscript_self.gd @@ -0,0 +1,8 @@ +# https://github.com/godotengine/godot/issues/43221 +extends Node + +func test(): + name = "Node" + print(self["name"]) + self["name"] = "Changed" + print(name) diff --git a/modules/gdscript/tests/scripts/analyzer/features/subscript_self.out b/modules/gdscript/tests/scripts/analyzer/features/subscript_self.out new file mode 100644 index 0000000000..6417f4f8da --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/subscript_self.out @@ -0,0 +1,3 @@ +GDTEST_OK +Node +Changed diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_inferred_access_isnt_constant.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_inferred_access_isnt_constant.gd new file mode 100644 index 0000000000..55c40cb971 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_inferred_access_isnt_constant.gd @@ -0,0 +1,6 @@ +# https://github.com/godotengine/godot/issues/53640 + +func test(): + var arr := [0] + arr[0] = 1 + print(arr[0]) diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_inferred_access_isnt_constant.out b/modules/gdscript/tests/scripts/analyzer/features/typed_array_inferred_access_isnt_constant.out new file mode 100644 index 0000000000..a7f1357bb2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_inferred_access_isnt_constant.out @@ -0,0 +1,2 @@ +GDTEST_OK +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_with_custom_class.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_with_custom_class.gd new file mode 100644 index 0000000000..9502f6e196 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_with_custom_class.gd @@ -0,0 +1,10 @@ +class Inner: + var prop = "Inner" + + +var array: Array[Inner] = [Inner.new()] + + +func test(): + var element: Inner = array[0] + print(element.prop) diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_with_custom_class.out b/modules/gdscript/tests/scripts/analyzer/features/typed_array_with_custom_class.out new file mode 100644 index 0000000000..8f250d2632 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_with_custom_class.out @@ -0,0 +1,2 @@ +GDTEST_OK +Inner diff --git a/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd new file mode 100644 index 0000000000..5f73064cc0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd @@ -0,0 +1,5 @@ +const preloaded : GDScript = preload("gdscript_to_preload.gd") + +func test(): + var preloaded_instance: preloaded = preloaded.new() + print(preloaded_instance.something()) diff --git a/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.out b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.out new file mode 100644 index 0000000000..1ccb591560 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.out @@ -0,0 +1,2 @@ +GDTEST_OK +OK diff --git a/modules/gdscript/tests/scripts/parser/errors/array_consecutive_commas.gd b/modules/gdscript/tests/scripts/parser/errors/array_consecutive_commas.gd new file mode 100644 index 0000000000..b45f99fdd0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/array_consecutive_commas.gd @@ -0,0 +1,3 @@ +func test(): + # Arrays with consecutive commas are not allowed. + var array = ["arrays",,,,] diff --git a/modules/gdscript/tests/scripts/parser/errors/array_consecutive_commas.out b/modules/gdscript/tests/scripts/parser/errors/array_consecutive_commas.out new file mode 100644 index 0000000000..4ef8526065 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/array_consecutive_commas.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression as array element. diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.gd new file mode 100644 index 0000000000..17d5e078e5 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.gd @@ -0,0 +1,2 @@ +func test(): + var hello == "world" diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.out b/modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.out new file mode 100644 index 0000000000..b150fc0d16 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_2_equal_signs.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected end of statement after variable declaration, found "==" instead. diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.gd new file mode 100644 index 0000000000..8b5f620889 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.gd @@ -0,0 +1,2 @@ +func test(): + var hello === "world" diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.out b/modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.out new file mode 100644 index 0000000000..b150fc0d16 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_3_equal_signs.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected end of statement after variable declaration, found "==" instead. diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.gd new file mode 100644 index 0000000000..17c65ad60a --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.gd @@ -0,0 +1,3 @@ +func test(): + var a = 0 + a = diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.out b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.out new file mode 100644 index 0000000000..1369a7a0dc --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_empty_assignee.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected an expression after "=". diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_if.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_in_if.gd new file mode 100644 index 0000000000..8c3a908532 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_if.gd @@ -0,0 +1,4 @@ +func test(): + # Error here. + if foo = 25: + print(foo) diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_if.out b/modules/gdscript/tests/scripts/parser/errors/assignment_in_if.out new file mode 100644 index 0000000000..e8f9130706 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_if.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Assignment is not allowed inside an expression. diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var.gd new file mode 100644 index 0000000000..126a3227ea --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var.gd @@ -0,0 +1,2 @@ +func test(): + var hello = "world" = "test" diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var.out b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var.out new file mode 100644 index 0000000000..e8f9130706 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Assignment is not allowed inside an expression. diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.gd new file mode 100644 index 0000000000..a99557fa3c --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.gd @@ -0,0 +1,4 @@ +func test(): + # Error here. + if var foo = 25: + print(foo) diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.out b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.out new file mode 100644 index 0000000000..e84f4652ac --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_in_var_if.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected conditional expression after "if". diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.gd b/modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.gd new file mode 100644 index 0000000000..031ea523c8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.gd @@ -0,0 +1,2 @@ +func test(): + var = "world" diff --git a/modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.out b/modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.out new file mode 100644 index 0000000000..a4bd8beef1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/assignment_without_identifier.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected variable name after "var". diff --git a/modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.gd b/modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.gd new file mode 100644 index 0000000000..b52a6defcb --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.gd @@ -0,0 +1,2 @@ +func test(): + print(~) diff --git a/modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.out b/modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.out new file mode 100644 index 0000000000..ceabe42d3c --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/binary_complement_without_argument.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression after "~" operator. diff --git a/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.gd b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.gd new file mode 100644 index 0000000000..b3ea1ba1f6 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.gd @@ -0,0 +1,2 @@ +func test(): + print(not) diff --git a/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.out b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.out new file mode 100644 index 0000000000..6cf191ea98 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression after "not" operator. diff --git a/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.gd b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.gd new file mode 100644 index 0000000000..8a33079193 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.gd @@ -0,0 +1,2 @@ +func test(): + print(!) diff --git a/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.out b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.out new file mode 100644 index 0000000000..87fcc5e2b0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/boolean_negation_without_argument_using_bang.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression after "!" operator. diff --git a/modules/gdscript/tests/scripts/parser/errors/brace_syntax.gd b/modules/gdscript/tests/scripts/parser/errors/brace_syntax.gd new file mode 100644 index 0000000000..ab66537c93 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/brace_syntax.gd @@ -0,0 +1,3 @@ +func test() { + print("Hello world!"); +} diff --git a/modules/gdscript/tests/scripts/parser/errors/brace_syntax.out b/modules/gdscript/tests/scripts/parser/errors/brace_syntax.out new file mode 100644 index 0000000000..2f37a740ab --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/brace_syntax.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected ":" after function declaration. diff --git a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd new file mode 100644 index 0000000000..d13d713454 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd @@ -0,0 +1,6 @@ +# Error here. `class_name` should be used *before* annotations, not after. +@icon("res://path/to/optional/icon.svg") +class_name HelloWorld + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out new file mode 100644 index 0000000000..0bcc8acc55 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +"class_name" should be used before annotations. diff --git a/modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.gd b/modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.gd new file mode 100644 index 0000000000..49fb4ffedf --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.gd @@ -0,0 +1,3 @@ +func test(): + var TEST = 50 + const TEST = 25 diff --git a/modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.out b/modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.out new file mode 100644 index 0000000000..407f094ca0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/constant_conflicts_variable.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +There is already a variable named "TEST" declared in this scope. diff --git a/modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.gd b/modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.gd new file mode 100644 index 0000000000..2581d873dd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.gd @@ -0,0 +1,7 @@ +func hello(arg1): + print(arg1) + + +func test(): + # Error here. + hello(arg1 = 25) diff --git a/modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.out b/modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.out new file mode 100644 index 0000000000..e8f9130706 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/default_value_in_function_call.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Assignment is not allowed inside an expression. diff --git a/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd new file mode 100644 index 0000000000..92dfb2366d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd @@ -0,0 +1,2 @@ +func test(): + var dictionary = { hello = "world",, } diff --git a/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.out b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.out new file mode 100644 index 0000000000..d1dcd1cb4b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression as dictionary key. diff --git a/modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.gd b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.gd new file mode 100644 index 0000000000..a8f7cf1810 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.gd @@ -0,0 +1,5 @@ +const test = 25 + + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.out b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.out new file mode 100644 index 0000000000..c614acd094 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_constant.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Function "test" has the same name as a previously declared constant. diff --git a/modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.gd b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.gd new file mode 100644 index 0000000000..5c86710a40 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.gd @@ -0,0 +1,7 @@ +func test(): + pass + + +# Error here. The difference with `variable-conflicts-function.gd` is that here, +# the function is defined *before* the variable. +var test = 25 diff --git a/modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.out b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.out new file mode 100644 index 0000000000..551db61531 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/function_conflicts_variable.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Variable "test" has the same name as a previously declared function. diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.gd b/modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.gd new file mode 100644 index 0000000000..3b52f6e324 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.gd @@ -0,0 +1,2 @@ +func test(): + var escape = "invalid escape \h <- here" diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.out b/modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.out new file mode 100644 index 0000000000..32b4d004db --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/invalid_escape_sequence.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Invalid escape in string. diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_number.gd b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_number.gd new file mode 100644 index 0000000000..081b9faf4b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_number.gd @@ -0,0 +1,3 @@ +func test(): + # Error here. + var 23test = "is not a valid identifier" diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_number.out b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_number.out new file mode 100644 index 0000000000..a4bd8beef1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_number.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected variable name after "var". diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.gd b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.gd new file mode 100644 index 0000000000..fa4d6b5cac --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.gd @@ -0,0 +1,3 @@ +func test(): + # Error here. + var "yes" = "is not a valid identifier" diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.out b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.out new file mode 100644 index 0000000000..a4bd8beef1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/invalid_identifier_string.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected variable name after "var". diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.gd b/modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.gd new file mode 100644 index 0000000000..c835ce15e1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.gd @@ -0,0 +1,5 @@ +func test(): + var amount = 50 + # C-style ternary operator is invalid in GDScript. + # The valid syntax is `"yes" if amount < 60 else "no"`, like in Python. + var ternary = amount < 60 ? "yes" : "no" diff --git a/modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.out b/modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.out new file mode 100644 index 0000000000..ac82d691b7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/invalid_ternary_operator.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Unexpected "?" in source. If you want a ternary operator, use "truthy_value if true_condition else falsy_value". diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_argument.gd b/modules/gdscript/tests/scripts/parser/errors/missing_argument.gd deleted file mode 100644 index c56ad94095..0000000000 --- a/modules/gdscript/tests/scripts/parser/errors/missing_argument.gd +++ /dev/null @@ -1,6 +0,0 @@ -func args(a, b): - print(a) - print(b) - -func test(): - args(1,) diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd b/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd index a1077e1985..8af5f123cc 100644 --- a/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd +++ b/modules/gdscript/tests/scripts/parser/errors/missing_closing_expr_paren.gd @@ -1,2 +1,2 @@ func test(): - var a = ("missing paren ->" + var a = ("missing paren ->" diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd b/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd index 62cb633e9e..0e5e5ce060 100644 --- a/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd +++ b/modules/gdscript/tests/scripts/parser/errors/missing_colon.gd @@ -1,3 +1,3 @@ func test(): - if true # Missing colon here. - print("true") + if true # Missing colon here. + print("true") diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.gd b/modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.gd new file mode 100644 index 0000000000..1f66935329 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.gd @@ -0,0 +1,4 @@ +func test(): + var x = 1 if false else + print("oops") + print(x) diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.out b/modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.out new file mode 100644 index 0000000000..dab6b0a1ad --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/missing_expression_after_ternary_else.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression after "else". diff --git a/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd b/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd index 116b0151da..7a35bf688c 100644 --- a/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd +++ b/modules/gdscript/tests/scripts/parser/errors/missing_paren_after_args.gd @@ -1,6 +1,6 @@ func args(a, b): - print(a) - print(b) + print(a) + print(b) func test(): - args(1,2 + args(1,2 diff --git a/modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.gd b/modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.gd new file mode 100644 index 0000000000..193f824702 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.gd @@ -0,0 +1,3 @@ +func test(): + var a = 0 + print(a--) diff --git a/modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.out b/modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.out new file mode 100644 index 0000000000..b6b577a277 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/mistaken_decrement_operator.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression after "-" operator. diff --git a/modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.gd b/modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.gd new file mode 100644 index 0000000000..035d27638c --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.gd @@ -0,0 +1,3 @@ +func test(): + var a = 0 + print(a++) diff --git a/modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.out b/modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.out new file mode 100644 index 0000000000..24eb76593a --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/mistaken_increment_operator.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression after "+" operator. diff --git a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out index 6390de9788..31bed2dbc7 100644 --- a/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out +++ b/modules/gdscript/tests/scripts/parser/errors/mixing_tabs_spaces.out @@ -1,2 +1,2 @@ GDTEST_PARSER_ERROR -Used "\t" for indentation instead " " as used before in the file. +Used tab character for indentation instead of space as used before in the file. diff --git a/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.gd b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.gd new file mode 100644 index 0000000000..71a03fbc0d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.gd @@ -0,0 +1,3 @@ +func test(): + # Number separators may not be placed right next to each other. + var __ = 1__23 diff --git a/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.out b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.out new file mode 100644 index 0000000000..71a3c2fd6a --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/multiple_number_separators.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Only one underscore can be used as a numeric separator. diff --git a/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd b/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd index 3875ce3936..df388a21de 100644 --- a/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd +++ b/modules/gdscript/tests/scripts/parser/errors/nothing_after_dollar.gd @@ -1,3 +1,5 @@ extends Node + + func test(): - var a = $ # Expected some node path. + var a = $ # Expected some node path. diff --git a/modules/gdscript/tests/scripts/parser/errors/redefine_keyword.gd b/modules/gdscript/tests/scripts/parser/errors/redefine_keyword.gd new file mode 100644 index 0000000000..c289c9d976 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/redefine_keyword.gd @@ -0,0 +1,2 @@ +func test(): + var while = "it's been a while" diff --git a/modules/gdscript/tests/scripts/parser/errors/redefine_keyword.out b/modules/gdscript/tests/scripts/parser/errors/redefine_keyword.out new file mode 100644 index 0000000000..a4bd8beef1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/redefine_keyword.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected variable name after "var". diff --git a/modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.gd b/modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.gd new file mode 100644 index 0000000000..204259f981 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.gd @@ -0,0 +1,5 @@ +func test(): + const TEST = 25 + + # Error here (can't redeclare a constant on the same scope). + const TEST = 50 diff --git a/modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.out b/modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.out new file mode 100644 index 0000000000..d67cc92953 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/redefine_local_constant_with_keyword.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +There is already a constant named "TEST" declared in this scope. diff --git a/modules/gdscript/tests/scripts/parser/errors/subscript_without_index.gd b/modules/gdscript/tests/scripts/parser/errors/subscript_without_index.gd new file mode 100644 index 0000000000..c30c05e4da --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/subscript_without_index.gd @@ -0,0 +1,3 @@ +func test(): + var array = [1, 2, 3] + array[] = 4 diff --git a/modules/gdscript/tests/scripts/parser/errors/subscript_without_index.out b/modules/gdscript/tests/scripts/parser/errors/subscript_without_index.out new file mode 100644 index 0000000000..7017c7b4aa --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/subscript_without_index.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression after "[". diff --git a/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.gd b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.gd new file mode 100644 index 0000000000..0d8843df20 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.gd @@ -0,0 +1,3 @@ +func test(): + const TEST = 25 + var TEST = 50 diff --git a/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.out b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.out new file mode 100644 index 0000000000..d67cc92953 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_constant.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +There is already a constant named "TEST" declared in this scope. diff --git a/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.gd b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.gd new file mode 100644 index 0000000000..ce2c8784d6 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.gd @@ -0,0 +1,6 @@ +var test = 25 + +# Error here. The difference with `variable-conflicts-function.gd` is that here, +# the function is defined *before* the variable. +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.out b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.out new file mode 100644 index 0000000000..daeaca40ec --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/variable_conflicts_function.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Function "test" has the same name as a previously declared variable. diff --git a/modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.gd b/modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.gd new file mode 100644 index 0000000000..8850892f2d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.gd @@ -0,0 +1,13 @@ +# The VCS conflict marker has only 6 `=` signs instead of 7 to prevent editors like +# Visual Studio Code from recognizing it as an actual VCS conflict marker. +# Nonetheless, the GDScript parser is still expected to find and report the VCS +# conflict marker error correctly. + +<<<<<<< HEAD +Hello world +====== +Goodbye +>>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086 + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.out b/modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.out new file mode 100644 index 0000000000..df9dab2223 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/vcs_conflict_marker.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Unexpected "VCS conflict marker" in class body. diff --git a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd index 6fd2692d47..babe39068c 100644 --- a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd +++ b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar.gd @@ -1,3 +1,5 @@ extends Node + + func test(): - $23 # Can't use number here. + $23 # Can't use number here. diff --git a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd index 1836d42226..b6b1cf3e52 100644 --- a/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd +++ b/modules/gdscript/tests/scripts/parser/errors/wrong_value_after_dollar_slash.gd @@ -1,3 +1,5 @@ extends Node + + func test(): - $MyNode/23 # Can't use number here. + $MyNode/23 # Can't use number here. diff --git a/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.gd b/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.gd new file mode 100644 index 0000000000..7862eff6ec --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.gd @@ -0,0 +1,6 @@ +#GDTEST_PARSER_ERROR + +signal event + +func test(): + yield("event") diff --git a/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.out b/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.out new file mode 100644 index 0000000000..36cb699e92 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/yield_instead_of_await.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +"yield" was removed in Godot 4.0. Use "await" instead. diff --git a/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd new file mode 100644 index 0000000000..43b513045b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd @@ -0,0 +1,34 @@ +func foo(x): + match x: + 1 + 1: + print("1+1") + [1,2,[1,{1:2,2:var z,..}]]: + print("[1,2,[1,{1:2,2:var z,..}]]") + print(z) + 1 if true else 2: + print("1 if true else 2") + 1 < 2: + print("1 < 2") + 1 or 2 and 1: + print("1 or 2 and 1") + 6 | 1: + print("1 | 1") + 1 >> 1: + print("1 >> 1") + 1, 2 or 3, 4: + print("1, 2 or 3, 4") + _: + print("wildcard") + +func test(): + foo(6 | 1) + foo(1 >> 1) + foo(2) + foo(1) + foo(1+1) + foo(1 < 2) + foo([2, 1]) + foo(4) + foo([1, 2, [1, {1 : 2, 2:3}]]) + foo([1, 2, [1, {1 : 2, 2:[1,3,5, "123"], 4:2}]]) + foo([1, 2, [1, {1 : 2}]]) diff --git a/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.out b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.out new file mode 100644 index 0000000000..67c7e28046 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.out @@ -0,0 +1,14 @@ +GDTEST_OK +1 | 1 +1 >> 1 +1+1 +1 if true else 2 +1+1 +1 < 2 +wildcard +1, 2 or 3, 4 +[1,2,[1,{1:2,2:var z,..}]] +3 +[1,2,[1,{1:2,2:var z,..}]] +[1, 3, 5, 123] +wildcard diff --git a/modules/gdscript/tests/scripts/parser/features/array.gd b/modules/gdscript/tests/scripts/parser/features/array.gd new file mode 100644 index 0000000000..828ce8d134 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/array.gd @@ -0,0 +1,16 @@ +func test(): + # Indexing from the beginning: + print([1, 2, 3][0]) + print([1, 2, 3][1]) + print([1, 2, 3][2]) + + # Indexing from the end: + print([1, 2, 3][-1]) + print([1, 2, 3][-2]) + print([1, 2, 3][-3]) + + # Float indices are currently allowed, but should probably be an error? + print([1, 2, 3][0.4]) + print([1, 2, 3][0.8]) + print([1, 2, 3][1.0]) + print([1, 2, 3][-1.0]) diff --git a/modules/gdscript/tests/scripts/parser/features/array.out b/modules/gdscript/tests/scripts/parser/features/array.out new file mode 100644 index 0000000000..cf576c59e0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/array.out @@ -0,0 +1,11 @@ +GDTEST_OK +1 +2 +3 +3 +2 +1 +1 +1 +2 +3 diff --git a/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd new file mode 100644 index 0000000000..2b46f1e88a --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd @@ -0,0 +1,27 @@ +func foo(x): + match x: + 1: + print("1") + 2: + print("2") + [1, 2]: + print("[1, 2]") + 3 or 4: + print("3 or 4") + 4: + print("4") + {1 : 2, 2 : 3}: + print("{1 : 2, 2 : 3}") + _: + print("wildcard") + +func test(): + foo(0) + foo(1) + foo(2) + foo([1, 2]) + foo(3) + foo(4) + foo([4,4]) + foo({1 : 2, 2 : 3}) + foo({1 : 2, 4 : 3}) diff --git a/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.out b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.out new file mode 100644 index 0000000000..46ee4b04da --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.out @@ -0,0 +1,10 @@ +GDTEST_OK +wildcard +1 +2 +[1, 2] +wildcard +4 +wildcard +{1 : 2, 2 : 3} +wildcard diff --git a/modules/gdscript/tests/scripts/parser/features/bitwise_operators.gd b/modules/gdscript/tests/scripts/parser/features/bitwise_operators.gd new file mode 100644 index 0000000000..de502c6ed1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/bitwise_operators.gd @@ -0,0 +1,50 @@ +enum Flags { + FIRE = 1 << 1, + ICE = 1 << 2, + SLIPPERY = 1 << 3, + STICKY = 1 << 4, + NONSOLID = 1 << 5, + + ALL = FIRE | ICE | SLIPPERY | STICKY | NONSOLID, +} + + +func test(): + var flags = Flags.FIRE | Flags.SLIPPERY + print(flags) + + flags = Flags.FIRE & Flags.SLIPPERY + print(flags) + + flags = Flags.FIRE ^ Flags.SLIPPERY + print(flags) + + flags = Flags.ALL & (Flags.FIRE | Flags.ICE) + print(flags) + + flags = (Flags.ALL & Flags.FIRE) | Flags.ICE + print(flags) + + flags = Flags.ALL & Flags.FIRE | Flags.ICE + print(flags) + + # Enum value must be casted to an integer. Otherwise, a parser error is emitted. + flags &= int(Flags.ICE) + print(flags) + + flags ^= int(Flags.ICE) + print(flags) + + flags |= int(Flags.STICKY | Flags.SLIPPERY) + print(flags) + + print() + + var num = 2 << 4 + print(num) + + num <<= 2 + print(num) + + num >>= 2 + print(num) diff --git a/modules/gdscript/tests/scripts/parser/features/bitwise_operators.out b/modules/gdscript/tests/scripts/parser/features/bitwise_operators.out new file mode 100644 index 0000000000..410e358a05 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/bitwise_operators.out @@ -0,0 +1,14 @@ +GDTEST_OK +10 +0 +10 +6 +6 +6 +4 +0 +24 + +32 +128 +32 diff --git a/modules/gdscript/tests/scripts/parser/features/class.gd b/modules/gdscript/tests/scripts/parser/features/class.gd new file mode 100644 index 0000000000..6652f85ad9 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/class.gd @@ -0,0 +1,25 @@ +# Test non-nested/slightly nested class architecture. +class Test: + var number = 25 + var string = "hello" + + +class TestSub extends Test: + var other_string = "bye" + + +class TestConstructor: + func _init(argument = 10): + print(str("constructor with argument ", argument)) + + +func test(): + var test_instance = Test.new() + test_instance.number = 42 + + var test_sub = TestSub.new() + assert(test_sub.number == 25) # From Test. + assert(test_sub.other_string == "bye") # From TestSub. + + TestConstructor.new() + TestConstructor.new(500) diff --git a/modules/gdscript/tests/scripts/parser/features/class.out b/modules/gdscript/tests/scripts/parser/features/class.out new file mode 100644 index 0000000000..94dc2d6003 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/class.out @@ -0,0 +1,3 @@ +GDTEST_OK +constructor with argument 10 +constructor with argument 500 diff --git a/modules/gdscript/tests/scripts/parser/features/class_inheritance.gd b/modules/gdscript/tests/scripts/parser/features/class_inheritance.gd new file mode 100644 index 0000000000..3f9b4ea86e --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/class_inheritance.gd @@ -0,0 +1,33 @@ +# Test deeply nested class architectures. +class Test: + var depth = 1 + + class Nested: + var depth_nested = 10 + + +class Test2 extends Test: + var depth2 = 2 + + +class Test3 extends Test2: + var depth3 = 3 + + +class Test4 extends Test3: + var depth4 = 4 + + class Nested2: + var depth4_nested = 100 + + +func test(): + print(Test.new().depth) + print(Test2.new().depth) + print(Test2.new().depth2) + print(Test3.new().depth) + print(Test3.new().depth3) + print(Test4.new().depth) + print(Test4.new().depth4) + print(Test.Nested.new().depth_nested) + print(Test4.Nested2.new().depth4_nested) diff --git a/modules/gdscript/tests/scripts/parser/features/class_inheritance.out b/modules/gdscript/tests/scripts/parser/features/class_inheritance.out new file mode 100644 index 0000000000..75bdde3d94 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/class_inheritance.out @@ -0,0 +1,10 @@ +GDTEST_OK +1 +1 +2 +1 +3 +1 +4 +10 +100 diff --git a/modules/gdscript/tests/scripts/parser/features/class_name.gd b/modules/gdscript/tests/scripts/parser/features/class_name.gd new file mode 100644 index 0000000000..8bd188e247 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/class_name.gd @@ -0,0 +1,5 @@ +class_name HelloWorld +@icon("res://path/to/optional/icon.svg") + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/features/class_name.out b/modules/gdscript/tests/scripts/parser/features/class_name.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/class_name.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/features/concatenation.gd b/modules/gdscript/tests/scripts/parser/features/concatenation.gd new file mode 100644 index 0000000000..e8335c9823 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/concatenation.gd @@ -0,0 +1,4 @@ +func test(): + print(20 + 20) + print("hello" + "world") + print([1, 2] + [3, 4]) diff --git a/modules/gdscript/tests/scripts/parser/features/concatenation.out b/modules/gdscript/tests/scripts/parser/features/concatenation.out new file mode 100644 index 0000000000..23bff08f49 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/concatenation.out @@ -0,0 +1,4 @@ +GDTEST_OK +40 +helloworld +[1, 2, 3, 4] diff --git a/modules/gdscript/tests/scripts/parser/features/constants.gd b/modules/gdscript/tests/scripts/parser/features/constants.gd new file mode 100644 index 0000000000..013c9c074f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/constants.gd @@ -0,0 +1,11 @@ +func test(): + const _TEST = 12 + 34 - 56 * 78 + const _STRING = "yes" + const _VECTOR = Vector2(5, 6) + const _ARRAY = [] + const _DICTIONARY = {"this": "dictionary"} + + # Create user constants from built-in constants. + const _HELLO = PI + TAU + const _INFINITY = INF + const _NOT_A_NUMBER = NAN diff --git a/modules/gdscript/tests/scripts/parser/features/constants.out b/modules/gdscript/tests/scripts/parser/features/constants.out new file mode 100644 index 0000000000..6093e4a6ca --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/constants.out @@ -0,0 +1,33 @@ +GDTEST_OK +>> WARNING +>> Line: 2 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_TEST' is declared but never used in the block. If this is intended, prefix it with an underscore: '__TEST' +>> WARNING +>> Line: 3 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_STRING' is declared but never used in the block. If this is intended, prefix it with an underscore: '__STRING' +>> WARNING +>> Line: 4 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_VECTOR' is declared but never used in the block. If this is intended, prefix it with an underscore: '__VECTOR' +>> WARNING +>> Line: 5 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_ARRAY' is declared but never used in the block. If this is intended, prefix it with an underscore: '__ARRAY' +>> WARNING +>> Line: 6 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_DICTIONARY' is declared but never used in the block. If this is intended, prefix it with an underscore: '__DICTIONARY' +>> WARNING +>> Line: 9 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_HELLO' is declared but never used in the block. If this is intended, prefix it with an underscore: '__HELLO' +>> WARNING +>> Line: 10 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_INFINITY' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INFINITY' +>> WARNING +>> Line: 11 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_NOT_A_NUMBER' is declared but never used in the block. If this is intended, prefix it with an underscore: '__NOT_A_NUMBER' diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary.gd b/modules/gdscript/tests/scripts/parser/features/dictionary.gd new file mode 100644 index 0000000000..99afe166c7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/dictionary.gd @@ -0,0 +1,37 @@ +func test(): + # Non-string keys are valid. + print({ 12: "world" }[12]) + + var contents = { + 0: "zero", + 0.0: "zero point zero", + null: "null", + false: "false", + []: "empty array", + Vector2i(): "zero Vector2i", + 15: { + 22: { + 4: ["nesting", "arrays"], + }, + }, + } + + print(contents[0.0]) + # Making sure declaration order doesn't affect things... + print({ 0.0: "zero point zero", 0: "zero", null: "null", false: "false", []: "empty array" }[0]) + print({ 0.0: "zero point zero", 0: "zero", null: "null", false: "false", []: "empty array" }[0.0]) + + print(contents[null]) + print(contents[false]) + print(contents[[]]) + print(contents[Vector2i()]) + print(contents[15]) + print(contents[15][22]) + print(contents[15][22][4]) + print(contents[15][22][4][0]) + print(contents[15][22][4][1]) + + # Currently fails with "invalid get index 'hello' on base Dictionary". + # Both syntaxes are valid however. + #print({ "hello": "world" }["hello"]) + #print({ "hello": "world" }.hello) diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary.out b/modules/gdscript/tests/scripts/parser/features/dictionary.out new file mode 100644 index 0000000000..54083c1afc --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/dictionary.out @@ -0,0 +1,14 @@ +GDTEST_OK +world +zero point zero +zero +zero point zero +null +false +empty array +zero Vector2i +{22:{4:[nesting, arrays]}} +{4:[nesting, arrays]} +[nesting, arrays] +nesting +arrays diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.gd b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.gd new file mode 100644 index 0000000000..fdd6de2348 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.gd @@ -0,0 +1,9 @@ +func test(): + var lua_dict = { + a = 1, + "b" = 2, # Using strings are allowed too. + "with spaces" = 3, # Especially useful when key has spaces... + "2" = 4, # ... or invalid identifiers. + } + + print(lua_dict) diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out new file mode 100644 index 0000000000..447d7e223c --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out @@ -0,0 +1,2 @@ +GDTEST_OK +{2:4, a:1, b:2, with spaces:3} diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.gd b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.gd new file mode 100644 index 0000000000..cce8538ddd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.gd @@ -0,0 +1,12 @@ +func test(): + # Mixing Python-style and Lua-style syntax in the same dictionary declaration + # is allowed. + var dict = { + "hello": { + world = { + "is": "beautiful", + }, + }, + } + + print(dict) diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out new file mode 100644 index 0000000000..62be807a1f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out @@ -0,0 +1,2 @@ +GDTEST_OK +{hello:{world:{is:beautiful}}} diff --git a/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd new file mode 100644 index 0000000000..8ba558e91d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.gd @@ -0,0 +1,20 @@ +extends Node + + +func test(): + # Create the required node structure. + var hello = Node.new() + hello.name = "Hello" + add_child(hello) + var world = Node.new() + world.name = "World" + hello.add_child(world) + + # All the ways of writing node paths below with the `$` operator are valid. + # Results are assigned to variables to avoid warnings. + var __ = $Hello + __ = $"Hello" + __ = $Hello/World + __ = $"Hello/World" + __ = $"Hello/.." + __ = $"Hello/../Hello/World" diff --git a/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.out b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/dollar_node_paths.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/features/enum.gd b/modules/gdscript/tests/scripts/parser/features/enum.gd new file mode 100644 index 0000000000..bbc66f6f3d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/enum.gd @@ -0,0 +1,14 @@ +enum Size { + S = -10, + M, + L = 0, + XL = 10, + XXL, +} + +func test(): + print(Size.S) + print(Size.M) + print(Size.L) + print(Size.XL) + print(Size.XXL) diff --git a/modules/gdscript/tests/scripts/parser/features/enum.out b/modules/gdscript/tests/scripts/parser/features/enum.out new file mode 100644 index 0000000000..6f3a4a3e49 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/enum.out @@ -0,0 +1,6 @@ +GDTEST_OK +-10 +-9 +0 +10 +11 diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd new file mode 100644 index 0000000000..1e072728fc --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd @@ -0,0 +1,18 @@ +@export var example = 99 +@export_range(0, 100) var example_range = 100 +@export_range(0, 100, 1) var example_range_step = 101 +@export_range(0, 100, 1, "or_greater") var example_range_step_or_greater = 102 + +@export var color: Color +@export_color_no_alpha var color_no_alpha: Color +@export_node_path(Sprite2D, Sprite3D, Control, Node) var nodepath := ^"hello" + + +func test(): + print(example) + print(example_range) + print(example_range_step) + print(example_range_step_or_greater) + print(color) + print(color_no_alpha) + print(nodepath) diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.out b/modules/gdscript/tests/scripts/parser/features/export_variable.out new file mode 100644 index 0000000000..bae35e75c6 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/export_variable.out @@ -0,0 +1,8 @@ +GDTEST_OK +99 +100 +101 +102 +(0, 0, 0, 1) +(0, 0, 0, 1) +hello diff --git a/modules/gdscript/tests/scripts/parser/features/float_notation.gd b/modules/gdscript/tests/scripts/parser/features/float_notation.gd new file mode 100644 index 0000000000..b207b88820 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/float_notation.gd @@ -0,0 +1,24 @@ +func test(): + # The following floating-point notations are all valid: + print(is_equal_approx(123., 123)) + print(is_equal_approx(.123, 0.123)) + print(is_equal_approx(.123e4, 1230)) + print(is_equal_approx(123.e4, 1.23e6)) + print(is_equal_approx(.123e-1, 0.0123)) + print(is_equal_approx(123.e-1, 12.3)) + + # Same as above, but with negative numbers. + print(is_equal_approx(-123., -123)) + print(is_equal_approx(-.123, -0.123)) + print(is_equal_approx(-.123e4, -1230)) + print(is_equal_approx(-123.e4, -1.23e6)) + print(is_equal_approx(-.123e-1, -0.0123)) + print(is_equal_approx(-123.e-1, -12.3)) + + # Same as above, but with explicit positive numbers (which is redundant). + print(is_equal_approx(+123., +123)) + print(is_equal_approx(+.123, +0.123)) + print(is_equal_approx(+.123e4, +1230)) + print(is_equal_approx(+123.e4, +1.23e6)) + print(is_equal_approx(+.123e-1, +0.0123)) + print(is_equal_approx(+123.e-1, +12.3)) diff --git a/modules/gdscript/tests/scripts/parser/features/float_notation.out b/modules/gdscript/tests/scripts/parser/features/float_notation.out new file mode 100644 index 0000000000..041c4439b0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/float_notation.out @@ -0,0 +1,19 @@ +GDTEST_OK +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true diff --git a/modules/gdscript/tests/scripts/parser/features/for_range.gd b/modules/gdscript/tests/scripts/parser/features/for_range.gd new file mode 100644 index 0000000000..fd1d002b82 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/for_range.gd @@ -0,0 +1,39 @@ +func test(): + for i in range(5): + print(i) + + print() + + # Equivalent to the above `for` loop: + for i in 5: + print(i) + + print() + + for i in range(1, 5): + print(i) + + print() + + for i in range(1, -5, -1): + print(i) + + print() + + for i in [2, 4, 6, -8]: + print(i) + + print() + + for i in [true, false]: + print(i) + + print() + + for i in [Vector2i(10, 20), Vector2i(-30, -40)]: + print(i) + + print() + + for i in "Hello_Unicôde_world!_🦄": + print(i) diff --git a/modules/gdscript/tests/scripts/parser/features/for_range.out b/modules/gdscript/tests/scripts/parser/features/for_range.out new file mode 100644 index 0000000000..50b2c856c5 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/for_range.out @@ -0,0 +1,58 @@ +GDTEST_OK +0 +1 +2 +3 +4 + +0 +1 +2 +3 +4 + +1 +2 +3 +4 + +1 +0 +-1 +-2 +-3 +-4 + +2 +4 +6 +-8 + +true +false + +(10, 20) +(-30, -40) + +H +e +l +l +o +_ +U +n +i +c +ô +d +e +_ +w +o +r +l +d +! +_ +🦄 diff --git a/modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.gd b/modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.gd new file mode 100644 index 0000000000..f5098b00ae --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.gd @@ -0,0 +1,5 @@ +func example(_number: int, _number2: int = 5, number3 := 10): + return number3 + +func test(): + print(example(3)) diff --git a/modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.out b/modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.out new file mode 100644 index 0000000000..404cd41fe5 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/function_default_parameter_type_inference.out @@ -0,0 +1,2 @@ +GDTEST_OK +10 diff --git a/modules/gdscript/tests/scripts/parser/features/function_many_parameters.gd b/modules/gdscript/tests/scripts/parser/features/function_many_parameters.gd new file mode 100644 index 0000000000..01edb37cec --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/function_many_parameters.gd @@ -0,0 +1,5 @@ +func example(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25, arg26, arg27, arg28, arg29, arg30, arg31, arg32, arg33, arg34, arg35, arg36, arg37, arg38, arg39, arg40, arg41, arg42, arg43, arg44, arg45, arg46, arg47, arg48 = false, arg49 = true, arg50 = null): + print(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25, arg26, arg27, arg28, arg29, arg30, arg31, arg32, arg33, arg34, arg35, arg36, arg37, arg38, arg39, arg40, arg41, arg42, arg43, arg44, arg45, arg46, arg47, arg48, arg49, arg50) + +func test(): + example(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47) diff --git a/modules/gdscript/tests/scripts/parser/features/function_many_parameters.out b/modules/gdscript/tests/scripts/parser/features/function_many_parameters.out new file mode 100644 index 0000000000..3a979227d4 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/function_many_parameters.out @@ -0,0 +1,2 @@ +GDTEST_OK +123456789101112131415161718192212223242526272829303132333435363738394041424344454647falsetruenull diff --git a/modules/gdscript/tests/scripts/parser/features/in.gd b/modules/gdscript/tests/scripts/parser/features/in.gd new file mode 100644 index 0000000000..f7296017c5 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/in.gd @@ -0,0 +1,14 @@ +func test(): + print("dot" in "Godot") + print(not "i" in "team") + + print(true in [true, false]) + print(not null in [true, false]) + print(null in [null]) + + print(26 in [8, 26, 64, 100]) + print(not Vector2i(10, 20) in [Vector2i(20, 10)]) + + print("apple" in { "apple": "fruit" }) + print("apple" in { "apple": null }) + print(not "apple" in { "fruit": "apple" }) diff --git a/modules/gdscript/tests/scripts/parser/features/in.out b/modules/gdscript/tests/scripts/parser/features/in.out new file mode 100644 index 0000000000..7533f6ff54 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/in.out @@ -0,0 +1,11 @@ +GDTEST_OK +true +true +true +true +true +true +true +true +true +true diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_callable.gd b/modules/gdscript/tests/scripts/parser/features/lambda_callable.gd new file mode 100644 index 0000000000..c3b2506156 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/lambda_callable.gd @@ -0,0 +1,4 @@ +func test(): + var my_lambda = func(x): + print(x) + my_lambda.call("hello") diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_callable.out b/modules/gdscript/tests/scripts/parser/features/lambda_callable.out new file mode 100644 index 0000000000..58774d2d3f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/lambda_callable.out @@ -0,0 +1,2 @@ +GDTEST_OK +hello diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.gd b/modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.gd new file mode 100644 index 0000000000..f081a0b6a7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.gd @@ -0,0 +1,4 @@ +func test(): + var x = 42 + var my_lambda = func(): print(x) + my_lambda.call() # Prints "42". diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.out b/modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.out new file mode 100644 index 0000000000..0982f3718c --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/lambda_capture_callable.out @@ -0,0 +1,2 @@ +GDTEST_OK +42 diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_named_callable.gd b/modules/gdscript/tests/scripts/parser/features/lambda_named_callable.gd new file mode 100644 index 0000000000..7971ca72a6 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/lambda_named_callable.gd @@ -0,0 +1,10 @@ +func i_take_lambda(lambda: Callable, param: String): + lambda.call(param) + + +func test(): + var my_lambda := func this_is_lambda(x): + print("Hello") + print("This is %s" % x) + + i_take_lambda(my_lambda, "a lambda") diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_named_callable.out b/modules/gdscript/tests/scripts/parser/features/lambda_named_callable.out new file mode 100644 index 0000000000..c627187d82 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/lambda_named_callable.out @@ -0,0 +1,3 @@ +GDTEST_OK +Hello +This is a lambda diff --git a/modules/gdscript/tests/scripts/parser/features/match.gd b/modules/gdscript/tests/scripts/parser/features/match.gd new file mode 100644 index 0000000000..4d05490aa5 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/match.gd @@ -0,0 +1,18 @@ +func test(): + var i = "Hello" + match i: + "Hello": + print("hello") + # This will fall through to the default case below. + continue + "Good bye": + print("bye") + _: + print("default") + + var j = 25 + match j: + 26: + print("This won't match") + _: + print("This will match") diff --git a/modules/gdscript/tests/scripts/parser/features/match.out b/modules/gdscript/tests/scripts/parser/features/match.out new file mode 100644 index 0000000000..732885c7a2 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/match.out @@ -0,0 +1,4 @@ +GDTEST_OK +hello +default +This will match diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_arrays.gd b/modules/gdscript/tests/scripts/parser/features/multiline_arrays.gd new file mode 100644 index 0000000000..3b30998853 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_arrays.gd @@ -0,0 +1,7 @@ +func test(): + var __ = [ + "this", + "is", "a","multiline", + + "array", "with mixed indentation and trailing comma", + ] diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_arrays.out b/modules/gdscript/tests/scripts/parser/features/multiline_arrays.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_arrays.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.gd b/modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.gd new file mode 100644 index 0000000000..e108cd23d4 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.gd @@ -0,0 +1,10 @@ +func test(): + var __ = { + "multiline": "dictionary","should": "work", + "even with": "a trailing comma", + } + + __ = { + this_also_applies = "to the", + lua_style_syntax = null, foo = null, + } diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.out b/modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_dictionaries.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_if.gd b/modules/gdscript/tests/scripts/parser/features/multiline_if.gd new file mode 100644 index 0000000000..86152f4543 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_if.gd @@ -0,0 +1,14 @@ +func test(): + # Line breaks are allowed within parentheses. + if ( + 1 == 1 + and 2 == 2 and + 3 == 3 + ): + pass + + # Alternatively, backslashes can be used. + if 1 == 1 \ + and 2 == 2 and \ + 3 == 3: + pass diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_if.out b/modules/gdscript/tests/scripts/parser/features/multiline_if.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_if.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_strings.gd b/modules/gdscript/tests/scripts/parser/features/multiline_strings.gd new file mode 100644 index 0000000000..7f5bba85e7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_strings.gd @@ -0,0 +1,15 @@ +func test(): + var __ = """ + This is a standalone string, not a multiline comment. + Writing both "double" quotes and 'simple' quotes is fine as + long as there is only ""one"" or ''two'' of those in a row, not more. + + If you have more quotes, they need to be escaped like this: \"\"\" + """ + __ = ''' + Another standalone string, this time with single quotes. + Writing both "double" quotes and 'simple' quotes is fine as + long as there is only ""one"" or ''two'' of those in a row, not more. + + If you have more quotes, they need to be escaped like this: \'\'\' + ''' diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_strings.out b/modules/gdscript/tests/scripts/parser/features/multiline_strings.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_strings.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_vector.gd b/modules/gdscript/tests/scripts/parser/features/multiline_vector.gd new file mode 100644 index 0000000000..11a40fc00e --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_vector.gd @@ -0,0 +1,17 @@ +func test(): + Vector2( + 1, + 2 + ) + + Vector3( + 3, + 3.5, + 4, # Trailing comma should work. + ) + + Vector2i(1, 2,) # Trailing comma should work. + + Vector3i(6, + 9, + 12) diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_vector.out b/modules/gdscript/tests/scripts/parser/features/multiline_vector.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_vector.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/features/nested_arithmetic.gd b/modules/gdscript/tests/scripts/parser/features/nested_arithmetic.gd new file mode 100644 index 0000000000..b9bd19c9c5 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_arithmetic.gd @@ -0,0 +1,22 @@ +func test(): + print(+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++2.718) + + print() + + print(------------------------------------------------------------------2.718) + print(-------------------------------------------------------------------2.718) + + print() + + print(~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~999) + + print() + + print(+-+-+-----+------------+++++++---++--++--+--+---+--++2.718) + + print() + + print(2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 2 / 0.444) + print(153023902390239 % 550 % 29 % 27 % 23 % 17) + print(2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2 >> 2) + print(8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8 ^ -8 ^ 8 ^ 8 ^ 8 ^ 8 ^ 8) diff --git a/modules/gdscript/tests/scripts/parser/features/nested_arithmetic.out b/modules/gdscript/tests/scripts/parser/features/nested_arithmetic.out new file mode 100644 index 0000000000..048cfbdfae --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_arithmetic.out @@ -0,0 +1,82 @@ +GDTEST_OK +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +>> WARNING +>> Line: 19 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. +2.718 + +2.718 +-2.718 + +-1000 + +-2.718 + +36900.9009009009 +2 +8192 +-8 diff --git a/modules/gdscript/tests/scripts/parser/features/nested_array.gd b/modules/gdscript/tests/scripts/parser/features/nested_array.gd new file mode 100644 index 0000000000..3caef96391 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_array.gd @@ -0,0 +1,5 @@ +func test(): + var array = [[[[[[[[[[15]]]]]]]]]] + print(array[0][0][0][0][0][0][0][0]) + print(array[0][0][0][0][0][0][0][0][0]) + print(array[0][0][0][0][0][0][0][0][0][0]) diff --git a/modules/gdscript/tests/scripts/parser/features/nested_array.out b/modules/gdscript/tests/scripts/parser/features/nested_array.out new file mode 100644 index 0000000000..46c6ce3874 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_array.out @@ -0,0 +1,4 @@ +GDTEST_OK +[[15]] +[15] +15 diff --git a/modules/gdscript/tests/scripts/parser/features/nested_dictionary.gd b/modules/gdscript/tests/scripts/parser/features/nested_dictionary.gd new file mode 100644 index 0000000000..d67e142156 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_dictionary.gd @@ -0,0 +1,6 @@ +func test(): + var dictionary = {1: {2: {3: {4: {5: {6: {7: {8: {"key": "value"}}}}}}}}} + print(dictionary[1][2][3][4][5][6][7]) + print(dictionary[1][2][3][4][5][6][7][8]) + print(dictionary[1][2][3][4][5][6][7][8].key) + print(dictionary[1][2][3][4][5][6][7][8]["key"]) diff --git a/modules/gdscript/tests/scripts/parser/features/nested_dictionary.out b/modules/gdscript/tests/scripts/parser/features/nested_dictionary.out new file mode 100644 index 0000000000..4009160439 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_dictionary.out @@ -0,0 +1,5 @@ +GDTEST_OK +{8:{key:value}} +{key:value} +value +value diff --git a/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd new file mode 100644 index 0000000000..59cdc7d6c2 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.gd @@ -0,0 +1,5 @@ +func foo(x): + return x + 1 + +func test(): + 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/nested_function_calls.out b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.out new file mode 100644 index 0000000000..28a6636a7b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_function_calls.out @@ -0,0 +1,2 @@ +GDTEST_OK +24 diff --git a/modules/gdscript/tests/scripts/parser/features/nested_if.gd b/modules/gdscript/tests/scripts/parser/features/nested_if.gd new file mode 100644 index 0000000000..7282d08497 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_if.gd @@ -0,0 +1,57 @@ +func test(): + # 20 levels of nesting (and then some). + if true: + print("1") + if true: + print("2") + if true: + print("3") + if true: + print("4") + if true: + print("5") + if true: + print("6") + if true: + print("7") + if true: + print("8") + if true: + print("9") + if true: + print("10") + if true: + print("11") + if true: + print("12") + if true: + print("13") + if true: + print("14") + if true: + print("15") + if true: + print("16") + if true: + print("17") + if true: + print("18") + if true: + print("19") + if true: + print("20") + if false: + print("End") + if true: + if true: + if true: + if true: + if true: + if true: + if true: + if true: + if true: + if true: + if true: + if true: + print("This won't be printed") diff --git a/modules/gdscript/tests/scripts/parser/features/nested_if.out b/modules/gdscript/tests/scripts/parser/features/nested_if.out new file mode 100644 index 0000000000..c2d2e29a06 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_if.out @@ -0,0 +1,21 @@ +GDTEST_OK +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 diff --git a/modules/gdscript/tests/scripts/parser/features/nested_match.gd b/modules/gdscript/tests/scripts/parser/features/nested_match.gd new file mode 100644 index 0000000000..aaddcc7e83 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_match.gd @@ -0,0 +1,79 @@ +func test(): + # 20 levels of nesting (and then some). + var number = 1234 + match number: + 1234: + print("1") + match number: + 1234: + print("2") + match number: + 1234: + print("3") + continue + _: + print("Should also be printed") + match number: + 1234: + print("4") + match number: + _: + print("5") + match number: + false: + print("Should not be printed") + true: + print("Should not be printed") + "hello": + print("Should not be printed") + 1234: + print("6") + match number: + _: + print("7") + match number: + 1234: + print("8") + match number: + _: + print("9") + match number: + 1234: + print("10") + match number: + _: + print("11") + match number: + 1234: + print("12") + match number: + _: + print("13") + match number: + 1234: + print("14") + match number: + _: + print("15") + match number: + _: + print("16") + match number: + 1234: + print("17") + match number: + _: + print("18") + match number: + 1234: + print("19") + match number: + _: + print("20") + match number: + []: + print("Should not be printed") + _: + print("Should not be printed") + 5678: + print("Should not be printed either") diff --git a/modules/gdscript/tests/scripts/parser/features/nested_match.out b/modules/gdscript/tests/scripts/parser/features/nested_match.out new file mode 100644 index 0000000000..651d76cc59 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_match.out @@ -0,0 +1,22 @@ +GDTEST_OK +1 +2 +3 +Should also be printed +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 diff --git a/modules/gdscript/tests/scripts/parser/features/nested_parentheses.gd b/modules/gdscript/tests/scripts/parser/features/nested_parentheses.gd new file mode 100644 index 0000000000..3fef73b9be --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_parentheses.gd @@ -0,0 +1,65 @@ +func test(): + (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((print("Hello world!")))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) + + print(((((((((((((((((((((((((((((((((((((((((((((((((((((((((("Hello world 2!")))))))))))))))))))))))))))))))))))))))))))))))))))))))))) + + print( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + ( + (2)) + ((4)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) diff --git a/modules/gdscript/tests/scripts/parser/features/nested_parentheses.out b/modules/gdscript/tests/scripts/parser/features/nested_parentheses.out new file mode 100644 index 0000000000..27221a56bb --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/nested_parentheses.out @@ -0,0 +1,4 @@ +GDTEST_OK +Hello world! +Hello world 2! +6 diff --git a/modules/gdscript/tests/scripts/parser/features/number_separators.gd b/modules/gdscript/tests/scripts/parser/features/number_separators.gd new file mode 100644 index 0000000000..f5f5661cae --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/number_separators.gd @@ -0,0 +1,12 @@ +func test(): + # `_` can be used as a separator for numbers in GDScript. + # It can be placed anywhere in the number, except at the beginning. + # Currently, GDScript in the `master` branch only allows using one separator + # per number. + # Results are assigned to variables to avoid warnings. + var __ = 1_23 + __ = 123_ # Trailing number separators are OK. + __ = 12_3 + __ = 123_456 + __ = 0x1234_5678 + __ = 0b1001_0101 diff --git a/modules/gdscript/tests/scripts/parser/features/number_separators.out b/modules/gdscript/tests/scripts/parser/features/number_separators.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/number_separators.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/features/operator_assign.gd b/modules/gdscript/tests/scripts/parser/features/operator_assign.gd new file mode 100644 index 0000000000..b5f07675ca --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/operator_assign.gd @@ -0,0 +1,8 @@ +func test(): + var i = 0 + i += 5 + i -= 4 + i *= 10 + i %= 8 + i /= 0.25 + print(round(i)) diff --git a/modules/gdscript/tests/scripts/parser/features/operator_assign.out b/modules/gdscript/tests/scripts/parser/features/operator_assign.out new file mode 100644 index 0000000000..b0cb63ef59 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/operator_assign.out @@ -0,0 +1,2 @@ +GDTEST_OK +8 diff --git a/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd new file mode 100644 index 0000000000..9e4b360fb2 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd @@ -0,0 +1,37 @@ +# 4.0+ replacement for `setget`: +var _backing: int = 0 +var property: + get: + return _backing + 1000 + set(value): + _backing = value - 1000 + + +func test(): + print("Not using self:") + print(property) + print(_backing) + property = 5000 + print(property) + print(_backing) + _backing = -50 + print(property) + print(_backing) + property = 5000 + print(property) + print(_backing) + + # In Godot 4.0 and later, using `self` no longer makes a difference for + # getter/setter execution in GDScript. + print("Using self:") + print(self.property) + print(self._backing) + self.property = 5000 + print(self.property) + print(self._backing) + self._backing = -50 + print(self.property) + print(self._backing) + self.property = 5000 + print(self.property) + print(self._backing) diff --git a/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out new file mode 100644 index 0000000000..560e0c3bd7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out @@ -0,0 +1,19 @@ +GDTEST_OK +Not using self: +1000 +0 +5000 +4000 +950 +-50 +5000 +4000 +Using self: +5000 +4000 +5000 +4000 +950 +-50 +5000 +4000 diff --git a/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd index 08f2eedb2d..d50776c25c 100644 --- a/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd +++ b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.gd @@ -1,2 +1,5 @@ func test(): - print("A"); print("B") + print("A"); print("B") + + # Multiple semicolons and whitespace between them is also valid. + print("A"); ;;;;; ; print("B");; diff --git a/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out index fc03f3efe8..bd7f38f516 100644 --- a/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out +++ b/modules/gdscript/tests/scripts/parser/features/semicolon_as_end_statement.out @@ -1,3 +1,5 @@ GDTEST_OK A B +A +B diff --git a/modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.gd b/modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.gd new file mode 100644 index 0000000000..0f4aebb459 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.gd @@ -0,0 +1,22 @@ +#GDTEST_OK + +func test(): + a(); + b(); + c(); + d(); + e(); + +func a(): print("a"); + +func b(): print("b1"); print("b2") + +func c(): print("c1"); print("c2"); + +func d(): + print("d1"); + print("d2") + +func e(): + print("e1"); + print("e2"); diff --git a/modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.out b/modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.out new file mode 100644 index 0000000000..387cbd8fc2 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/semicolon_as_terminator.out @@ -0,0 +1,10 @@ +GDTEST_OK +a +b1 +b2 +c1 +c2 +d1 +d2 +e1 +e2 diff --git a/modules/gdscript/tests/scripts/parser/features/signal_declaration.gd b/modules/gdscript/tests/scripts/parser/features/signal_declaration.gd new file mode 100644 index 0000000000..9ad98b78a8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/signal_declaration.gd @@ -0,0 +1,20 @@ +#GDTEST_OK + +# No parentheses. +signal a + +# No parameters. +signal b() + +# With paramters. +signal c(a, b, c) + +# With parameters multiline. +signal d( + a, + b, + c, +) + +func test(): + print("Ok") diff --git a/modules/gdscript/tests/scripts/parser/features/signal_declaration.out b/modules/gdscript/tests/scripts/parser/features/signal_declaration.out new file mode 100644 index 0000000000..0e9f482af4 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/signal_declaration.out @@ -0,0 +1,2 @@ +GDTEST_OK +Ok diff --git a/modules/gdscript/tests/scripts/parser/features/single_line_declaration.gd b/modules/gdscript/tests/scripts/parser/features/single_line_declaration.gd new file mode 100644 index 0000000000..650500663b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/single_line_declaration.gd @@ -0,0 +1,11 @@ +#GDTEST_OK + +func test(): C.new().test("Ok"); test2() + +func test2(): print("Ok 2") + +class A: pass + +class B extends RefCounted: pass + +class C extends RefCounted: func test(x): print(x) diff --git a/modules/gdscript/tests/scripts/parser/features/single_line_declaration.out b/modules/gdscript/tests/scripts/parser/features/single_line_declaration.out new file mode 100644 index 0000000000..e021923dc2 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/single_line_declaration.out @@ -0,0 +1,3 @@ +GDTEST_OK +Ok +Ok 2 diff --git a/modules/gdscript/tests/scripts/parser/features/space_indentation.gd b/modules/gdscript/tests/scripts/parser/features/space_indentation.gd new file mode 100644 index 0000000000..0a4887c199 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/space_indentation.gd @@ -0,0 +1,4 @@ +func test(): + # 2-space indentation should work, even though the GDScript style guide recommends tabs. + if true: + pass diff --git a/modules/gdscript/tests/scripts/parser/features/space_indentation.out b/modules/gdscript/tests/scripts/parser/features/space_indentation.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/space_indentation.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/features/static_typing.gd b/modules/gdscript/tests/scripts/parser/features/static_typing.gd new file mode 100644 index 0000000000..d42632c82d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/static_typing.gd @@ -0,0 +1,13 @@ +func test(): + # The following lines are equivalent: + var _integer: int = 1 + var _integer2 : int = 1 + var _inferred := 1 + var _inferred2 : = 1 + + # Type inference is automatic for constants. + const _INTEGER = 1 + const _INTEGER_REDUNDANT_TYPED : int = 1 + const _INTEGER_REDUNDANT_TYPED2 : int = 1 + const _INTEGER_REDUNDANT_INFERRED := 1 + const _INTEGER_REDUNDANT_INFERRED2 : = 1 diff --git a/modules/gdscript/tests/scripts/parser/features/static_typing.out b/modules/gdscript/tests/scripts/parser/features/static_typing.out new file mode 100644 index 0000000000..92ce7bc0e0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/static_typing.out @@ -0,0 +1,21 @@ +GDTEST_OK +>> WARNING +>> Line: 9 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_INTEGER' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER' +>> WARNING +>> Line: 10 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_INTEGER_REDUNDANT_TYPED' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER_REDUNDANT_TYPED' +>> WARNING +>> Line: 11 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_INTEGER_REDUNDANT_TYPED2' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER_REDUNDANT_TYPED2' +>> WARNING +>> Line: 12 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_INTEGER_REDUNDANT_INFERRED' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER_REDUNDANT_INFERRED' +>> WARNING +>> Line: 13 +>> UNUSED_LOCAL_CONSTANT +>> The local constant '_INTEGER_REDUNDANT_INFERRED2' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER_REDUNDANT_INFERRED2' diff --git a/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd new file mode 100644 index 0000000000..9e48a7f0da --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.gd @@ -0,0 +1,7 @@ +func test(): + var null_var = null + var true_var: bool = true + var false_var: bool = false + print(str(null_var)) + print(str(true_var)) + print(str(false_var)) diff --git a/modules/gdscript/tests/scripts/parser/features/str_preserves_case.out b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.out new file mode 100644 index 0000000000..abba38e87c --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/str_preserves_case.out @@ -0,0 +1,4 @@ +GDTEST_OK +null +true +false diff --git a/modules/gdscript/tests/scripts/parser/features/string_formatting.gd b/modules/gdscript/tests/scripts/parser/features/string_formatting.gd new file mode 100644 index 0000000000..a91837145d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/string_formatting.gd @@ -0,0 +1,18 @@ +func test(): + print("hello %s" % "world" == "hello world") + print("hello %s" % true == "hello true") + print("hello %s" % false == "hello false") + + print("hello %d" % 25 == "hello 25") + print("hello %d %d" % [25, 42] == "hello 25 42") + # Pad with spaces. + print("hello %3d" % 25 == "hello 25") + # Pad with zeroes. + print("hello %03d" % 25 == "hello 025") + + print("hello %.02f" % 0.123456 == "hello 0.12") + + # Dynamic padding: + # <https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/gdscript_format_string.html#dynamic-padding> + print("hello %*.*f" % [7, 3, 0.123456] == "hello 0.123") + print("hello %0*.*f" % [7, 3, 0.123456] == "hello 000.123") diff --git a/modules/gdscript/tests/scripts/parser/features/string_formatting.out b/modules/gdscript/tests/scripts/parser/features/string_formatting.out new file mode 100644 index 0000000000..7533f6ff54 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/string_formatting.out @@ -0,0 +1,11 @@ +GDTEST_OK +true +true +true +true +true +true +true +true +true +true diff --git a/modules/gdscript/tests/scripts/parser/features/super.gd b/modules/gdscript/tests/scripts/parser/features/super.gd new file mode 100644 index 0000000000..f5ae2a74a7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/super.gd @@ -0,0 +1,60 @@ +class Say: + var prefix = "S" + + func greet(): + prefix = "S Greeted" + print("hello") + + func say(name): + print(prefix, " say something ", name) + + +class SayAnotherThing extends Say: + # This currently crashes the engine. + #var prefix = "SAT" + + func greet(): + prefix = "SAT Greeted" + print("hi") + + func say(name): + print(prefix, " say another thing ", name) + + +class SayNothing extends Say: + # This currently crashes the engine. + #var prefix = "SN" + + func greet(): + super() + prefix = "SN Greeted" + print("howdy, see above") + + func greet_prefix_before_super(): + prefix = "SN Greeted" + super.greet() + print("howdy, see above") + + func say(name): + super(name + " super'd") + print(prefix, " say nothing... or not? ", name) + + +func test(): + var say = Say.new() + say.greet() + say.say("foo") + print() + + var say_another_thing = SayAnotherThing.new() + say_another_thing.greet() + say_another_thing.say("bar") + print() + + var say_nothing = SayNothing.new() + say_nothing.greet() + print(say_nothing.prefix) + say_nothing.greet_prefix_before_super() + print(say_nothing.prefix) + # This currently triggers a compiler bug: "compiler bug, function name not found" + #say_nothing.say("baz") diff --git a/modules/gdscript/tests/scripts/parser/features/super.out b/modules/gdscript/tests/scripts/parser/features/super.out new file mode 100644 index 0000000000..e0d4f4f098 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/super.out @@ -0,0 +1,13 @@ +GDTEST_OK +hello +S Greeted say something foo + +hi +SAT Greeted say another thing bar + +hello +howdy, see above +SN Greeted +hello +howdy, see above +S Greeted diff --git a/modules/gdscript/tests/scripts/parser/features/truthiness.gd b/modules/gdscript/tests/scripts/parser/features/truthiness.gd new file mode 100644 index 0000000000..9c67a152f5 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/truthiness.gd @@ -0,0 +1,30 @@ +func test(): + # The assertions below should all evaluate to `true` for this test to pass. + assert(true) + assert(not false) + assert(500) + assert(not 0) + assert(500.5) + assert(not 0.0) + assert("non-empty string") + assert(["non-empty array"]) + assert({"non-empty": "dictionary"}) + assert(Vector2(1, 0)) + assert(Vector2i(-1, -1)) + assert(Vector3(0, 0, 0.0001)) + assert(Vector3i(0, 0, 10000)) + + # Zero position is `true` only if the Rect2's size is non-zero. + assert(Rect2(0, 0, 0, 1)) + + # Zero size is `true` only if the position is non-zero. + assert(Rect2(1, 1, 0, 0)) + + # Zero position is `true` only if the Rect2's size is non-zero. + assert(Rect2i(0, 0, 0, 1)) + + # Zero size is `true` only if the position is non-zero. + assert(Rect2i(1, 1, 0, 0)) + + # A fully black color is only truthy if its alpha component is not equal to `1`. + assert(Color(0, 0, 0, 0.5)) diff --git a/modules/gdscript/tests/scripts/parser/features/truthiness.out b/modules/gdscript/tests/scripts/parser/features/truthiness.out new file mode 100644 index 0000000000..705524857b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/truthiness.out @@ -0,0 +1,65 @@ +GDTEST_OK +>> WARNING +>> Line: 3 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 4 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 5 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 6 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 7 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 8 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 9 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 12 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 13 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 14 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 15 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 18 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 21 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 24 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 27 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 30 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. diff --git a/modules/gdscript/tests/scripts/parser/features/typed_arrays.gd b/modules/gdscript/tests/scripts/parser/features/typed_arrays.gd new file mode 100644 index 0000000000..21bf3fdfcf --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/typed_arrays.gd @@ -0,0 +1,5 @@ +func test(): + var my_array: Array[int] = [1, 2, 3] + var inferred_array := [1, 2, 3] # This is Array[int]. + print(my_array) + print(inferred_array) diff --git a/modules/gdscript/tests/scripts/parser/features/typed_arrays.out b/modules/gdscript/tests/scripts/parser/features/typed_arrays.out new file mode 100644 index 0000000000..953d54d5e0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/typed_arrays.out @@ -0,0 +1,3 @@ +GDTEST_OK +[1, 2, 3] +[1, 2, 3] diff --git a/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd b/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd index 3b48f10ca7..65013c4301 100644 --- a/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd +++ b/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd @@ -1,12 +1,20 @@ -var a # No init. -var b = 42 # Init. +var m1 # No init. +var m2 = 22 # Init. +var m3: String # No init, typed. +var m4: String = "44" # Init, typed. + func test(): - var c # No init, local. - var d = 23 # Init, local. + var loc5 # No init, local. + var loc6 = 66 # Init, local. + var loc7: String # No init, typed. + var loc8: String = "88" # Init, typed. + + m1 = 11 + m3 = "33" - a = 1 - c = 2 + loc5 = 55 + loc7 = "77" - prints(a, b, c, d) + prints(m1, m2, m3, m4, loc5, loc6, loc7, loc8) print("OK") diff --git a/modules/gdscript/tests/scripts/parser/features/variable_declaration.out b/modules/gdscript/tests/scripts/parser/features/variable_declaration.out index 2e0a63c024..7817dd3169 100644 --- a/modules/gdscript/tests/scripts/parser/features/variable_declaration.out +++ b/modules/gdscript/tests/scripts/parser/features/variable_declaration.out @@ -1,7 +1,3 @@ GDTEST_OK ->> WARNING ->> Line: 5 ->> UNASSIGNED_VARIABLE ->> The variable 'c' was used but never assigned a value. -1 42 2 23 +11 22 33 44 55 66 77 88 OK diff --git a/modules/gdscript/tests/scripts/parser/features/while.gd b/modules/gdscript/tests/scripts/parser/features/while.gd new file mode 100644 index 0000000000..17dd4fbad2 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/while.gd @@ -0,0 +1,5 @@ +func test(): + var i = 0 + while i < 5: + print(i) + i += 1 diff --git a/modules/gdscript/tests/scripts/parser/features/while.out b/modules/gdscript/tests/scripts/parser/features/while.out new file mode 100644 index 0000000000..b4a50885c7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/while.out @@ -0,0 +1,6 @@ +GDTEST_OK +0 +1 +2 +3 +4 diff --git a/modules/gdscript/tests/scripts/parser/warnings/assert_always_true.gd b/modules/gdscript/tests/scripts/parser/warnings/assert_always_true.gd new file mode 100644 index 0000000000..8feaed899f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/assert_always_true.gd @@ -0,0 +1,5 @@ +func test(): + # These statements always evaluate to `true`, and therefore emit a warning. + assert(true) + assert(-1.234) + assert(2 + 3 == 5) diff --git a/modules/gdscript/tests/scripts/parser/warnings/assert_always_true.out b/modules/gdscript/tests/scripts/parser/warnings/assert_always_true.out new file mode 100644 index 0000000000..5132792cb7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/assert_always_true.out @@ -0,0 +1,13 @@ +GDTEST_OK +>> WARNING +>> Line: 3 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 4 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. +>> WARNING +>> Line: 5 +>> ASSERT_ALWAYS_TRUE +>> Assert statement is redundant because the expression is always true. diff --git a/modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.gd b/modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.gd new file mode 100644 index 0000000000..f72b10213f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.gd @@ -0,0 +1,8 @@ +func test(): + # `and` should be used instead. + if true && true: + pass + + # `or` should be used instead. + if false || true: + pass diff --git a/modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.out b/modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/deprecated_operators.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.gd new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.gd @@ -0,0 +1 @@ + diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.out new file mode 100644 index 0000000000..20eec212ba --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.out @@ -0,0 +1,4 @@ +>> WARNING +>> Line: 1 +>> EMPTY_FILE +>> Empty script file. diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.gd new file mode 100644 index 0000000000..15cd95ff2b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.gd @@ -0,0 +1 @@ +#a comment diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.out new file mode 100644 index 0000000000..20eec212ba --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.out @@ -0,0 +1,4 @@ +>> WARNING +>> Line: 1 +>> EMPTY_FILE +>> Empty script file. diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.gd new file mode 100644 index 0000000000..b28b04f643 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.gd @@ -0,0 +1,3 @@ + + + diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.out new file mode 100644 index 0000000000..20eec212ba --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.out @@ -0,0 +1,4 @@ +>> WARNING +>> Line: 1 +>> EMPTY_FILE +>> Empty script file. diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.gd new file mode 100644 index 0000000000..ecdba44d21 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.gd @@ -0,0 +1,4 @@ +#a comment, followed by a bunch of newlines + + + diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.out new file mode 100644 index 0000000000..20eec212ba --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.out @@ -0,0 +1,4 @@ +>> WARNING +>> Line: 1 +>> EMPTY_FILE +>> Empty script file. diff --git a/modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.gd b/modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.gd new file mode 100644 index 0000000000..a93ecb66b1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.gd @@ -0,0 +1,8 @@ +func test(): + # The ternary operator below returns values of different types and the + # result is assigned to a typed variable. This will cause a run-time error + # if the branch with the incompatible type is picked. Here, it won't happen + # since the `false` condition never evaluates to `true`. Instead, a warning + # will be emitted. + var __: int = 25 + __ = "hello" if false else -2 diff --git a/modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.out b/modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.out new file mode 100644 index 0000000000..7d1558c6fc --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/incompatible_ternary.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 8 +>> INCOMPATIBLE_TERNARY +>> Values of the ternary conditional are not mutually compatible. diff --git a/modules/gdscript/tests/scripts/parser/warnings/integer_division.gd b/modules/gdscript/tests/scripts/parser/warnings/integer_division.gd new file mode 100644 index 0000000000..6117425528 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/integer_division.gd @@ -0,0 +1,10 @@ +func test(): + # This should emit a warning. + var __ = 5 / 2 + + # These should not emit warnings. + __ = float(5) / 2 + __ = 5 / float(2) + __ = 5.0 / 2 + __ = 5 / 2.0 + __ = 5.0 / 2.0 diff --git a/modules/gdscript/tests/scripts/parser/warnings/integer_division.out b/modules/gdscript/tests/scripts/parser/warnings/integer_division.out new file mode 100644 index 0000000000..40eb63ffcb --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/integer_division.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 3 +>> INTEGER_DIVISION +>> Integer division, decimal part will be discarded. diff --git a/modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.gd b/modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.gd new file mode 100644 index 0000000000..1eb54059dd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.gd @@ -0,0 +1,9 @@ +func test(): + var i = 25 + # The default branch (`_`) should be at the end of the `match` statement. + # Otherwise, a warning will be emitted + match i: + _: + print("default") + 25: + print("is 25") diff --git a/modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.out b/modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.out new file mode 100644 index 0000000000..8630fab420 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/match_default_not_at_end.out @@ -0,0 +1,6 @@ +GDTEST_OK +>> WARNING +>> Line: 8 +>> UNREACHABLE_PATTERN +>> Unreachable pattern (pattern after wildcard or bind). +default diff --git a/modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.gd b/modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.gd new file mode 100644 index 0000000000..954e697145 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.gd @@ -0,0 +1,5 @@ +func i_accept_ints_only(_i: int): + pass + +func test(): + i_accept_ints_only(12.345) diff --git a/modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.out b/modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.out new file mode 100644 index 0000000000..6fb592117b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/narrowing_conversion.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> NARROWING_CONVERSION +>> Narrowing conversion (float is converted to int and loses precision). diff --git a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.gd b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.gd new file mode 100644 index 0000000000..00598e4d50 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.gd @@ -0,0 +1,6 @@ +func i_return_int() -> int: + return 4 + + +func test(): + i_return_int() diff --git a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.gd b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.gd new file mode 100644 index 0000000000..d565d38365 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.gd @@ -0,0 +1,8 @@ +# See also `parser-errors/redefine-class-constant.gd`. +const TEST = 25 + + +func test(): + # Warning here. This is not an error because a new constant is created, + # rather than attempting to set the value of an existing constant. + const TEST = 50 diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out new file mode 100644 index 0000000000..9c9417e11d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out @@ -0,0 +1,9 @@ +GDTEST_OK +>> WARNING +>> Line: 8 +>> UNUSED_LOCAL_CONSTANT +>> The local constant 'TEST' is declared but never used in the block. If this is intended, prefix it with an underscore: '_TEST' +>> WARNING +>> Line: 8 +>> SHADOWED_VARIABLE +>> The local constant "TEST" is shadowing an already-declared constant at line 2. diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_global_identifier.gd b/modules/gdscript/tests/scripts/parser/warnings/shadowed_global_identifier.gd new file mode 100644 index 0000000000..3c64be571b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_global_identifier.gd @@ -0,0 +1,2 @@ +func test(): + var abs = "This variable has the same name as the built-in function." diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_global_identifier.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_global_identifier.out new file mode 100644 index 0000000000..f2b29e5bad --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_global_identifier.out @@ -0,0 +1,9 @@ +GDTEST_OK +>> WARNING +>> Line: 2 +>> SHADOWED_GLOBAL_IDENTIFIER +>> The local variable 'abs' has the same name as a built-in function. +>> WARNING +>> Line: 2 +>> UNUSED_VARIABLE +>> The local variable 'abs' is declared but never used in the block. If this is intended, prefix it with an underscore: '_abs' diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.gd b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.gd new file mode 100644 index 0000000000..66dcf309e8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.gd @@ -0,0 +1,8 @@ +var foo = 123 + + +func test(): + # Notice the `var` keyword. Without this keyword, no warning would be emitted + # because no new variable would be created. Instead, the class variable's value + # would be overwritten. + var foo = 456 diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out new file mode 100644 index 0000000000..82e467b368 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out @@ -0,0 +1,9 @@ +GDTEST_OK +>> WARNING +>> Line: 8 +>> UNUSED_VARIABLE +>> The local variable 'foo' is declared but never used in the block. If this is intended, prefix it with an underscore: '_foo' +>> WARNING +>> Line: 8 +>> SHADOWED_VARIABLE +>> The local variable "foo" is shadowing an already-declared variable at line 1. diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.gd b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.gd new file mode 100644 index 0000000000..2c55d68be8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.gd @@ -0,0 +1,2 @@ +func test(): + var test = "This variable has the same name as the test() function." diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out new file mode 100644 index 0000000000..26ce0465b1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out @@ -0,0 +1,9 @@ +GDTEST_OK +>> WARNING +>> Line: 2 +>> UNUSED_VARIABLE +>> The local variable 'test' is declared but never used in the block. If this is intended, prefix it with an underscore: '_test' +>> WARNING +>> Line: 2 +>> SHADOWED_VARIABLE +>> The local variable "test" is shadowing an already-declared function at line 1. diff --git a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd new file mode 100644 index 0000000000..18ea260fa2 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd @@ -0,0 +1,9 @@ +func test(): + # The following statements should all be reported as standalone expressions: + "This is a standalone expression" + 1234 + 0.0 + 0.0 + Color(1, 1, 1) + Vector3.ZERO + [true, false] + float(125) diff --git a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out new file mode 100644 index 0000000000..99ec87438e --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out @@ -0,0 +1,21 @@ +GDTEST_OK +>> WARNING +>> Line: 3 +>> STANDALONE_EXPRESSION +>> Standalone expression (the line has no effect). +>> WARNING +>> Line: 4 +>> STANDALONE_EXPRESSION +>> Standalone expression (the line has no effect). +>> WARNING +>> Line: 5 +>> STANDALONE_EXPRESSION +>> Standalone expression (the line has no effect). +>> WARNING +>> Line: 7 +>> STANDALONE_EXPRESSION +>> Standalone expression (the line has no effect). +>> WARNING +>> Line: 8 +>> STANDALONE_EXPRESSION +>> Standalone expression (the line has no effect). diff --git a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.gd b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.gd new file mode 100644 index 0000000000..afb5059eea --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.gd @@ -0,0 +1,2 @@ +func test(): + var __ diff --git a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.out b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.out new file mode 100644 index 0000000000..cf14502e9a --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 2 +>> UNASSIGNED_VARIABLE +>> The variable '__' was used but never assigned a value. diff --git a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.gd b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.gd new file mode 100644 index 0000000000..d77791f4c5 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.gd @@ -0,0 +1,4 @@ +func test(): + var __: int + # Variable has no set value at this point (even though it's implicitly `0` here). + __ += 15 diff --git a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.out b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.out new file mode 100644 index 0000000000..ba55a4e0f8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 4 +>> UNASSIGNED_VARIABLE_OP_ASSIGN +>> Using assignment with operation but the variable '__' was not previously assigned a value. diff --git a/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.gd b/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.gd new file mode 100644 index 0000000000..3311f342ab --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.gd @@ -0,0 +1,7 @@ +func test(): + var i = 25 + + return + + # This will never be run due to the `return` statement above. + print(i) diff --git a/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.out b/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.out new file mode 100644 index 0000000000..9316abd5eb --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 7 +>> UNREACHABLE_CODE +>> Unreachable code (statement after return) in function 'test()'. diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_argument.gd b/modules/gdscript/tests/scripts/parser/warnings/unused_argument.gd new file mode 100644 index 0000000000..e6e24dc6f2 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unused_argument.gd @@ -0,0 +1,12 @@ +# This should emit a warning since the unused argument is not prefixed with an underscore. +func function_with_unused_argument(p_arg1, p_arg2): + print(p_arg1) + + +# This shouldn't emit a warning since the unused argument is prefixed with an underscore. +func function_with_ignored_unused_argument(p_arg1, _p_arg2): + print(p_arg1) + + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_argument.out b/modules/gdscript/tests/scripts/parser/warnings/unused_argument.out new file mode 100644 index 0000000000..92f3308f85 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unused_argument.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 2 +>> UNUSED_PARAMETER +>> The parameter 'p_arg2' is never used in the function 'function_with_unused_argument'. If this is intended, prefix it with an underscore: '_p_arg2' diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd index 68e3bd424f..013a2e4beb 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd +++ b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.gd @@ -1,2 +1,4 @@ func test(): - var unused = "not used" + var unused = "not used" + + var _unused = "not used, but no warning since the variable name starts with an underscore" diff --git a/modules/gdscript/tests/scripts/parser/warnings/void_assignment.gd b/modules/gdscript/tests/scripts/parser/warnings/void_assignment.gd new file mode 100644 index 0000000000..b4a42b3e3d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/void_assignment.gd @@ -0,0 +1,6 @@ +func i_return_void() -> void: + return + + +func test(): + var __ = i_return_void() diff --git a/modules/gdscript/tests/scripts/parser/warnings/void_assignment.out b/modules/gdscript/tests/scripts/parser/warnings/void_assignment.out new file mode 100644 index 0000000000..84c9598f9a --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/void_assignment.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 6 +>> VOID_ASSIGNMENT +>> Assignment operation, but the function 'i_return_void()' returns void. diff --git a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd new file mode 100644 index 0000000000..10780b5379 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd @@ -0,0 +1,5 @@ +func test(): + var node := Node.new() + var inside_tree = node.is_inside_tree + node.free() + inside_tree.call() diff --git a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out new file mode 100644 index 0000000000..e585c374e2 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/callable_call_after_free_object.gd +>> 5 +>> Attempt to call function 'null::is_inside_tree (Callable)' on a null instance. diff --git a/modules/gdscript/tests/scripts/runtime/features/arrays_arent_shared.gd b/modules/gdscript/tests/scripts/runtime/features/arrays_arent_shared.gd new file mode 100644 index 0000000000..18174eae67 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/arrays_arent_shared.gd @@ -0,0 +1,32 @@ +# https://github.com/godotengine/godot/issues/48121 + +func test(): + var x := [] + var y := [] + x.push_back(y) + print("TEST ARRAY ADD TO SELF: " + str(len(y))) + x.clear() + + x = Array() + y = Array() + x.push_back(y) + print("TEST ARRAY ADD TO SELF: " + str(len(y))) + x.clear() + + x = Array().duplicate() + y = Array().duplicate() + x.push_back(y) + print("TEST ARRAY ADD TO SELF: " + str(len(y))) + x.clear() + + x = [].duplicate() + y = [].duplicate() + x.push_back(y) + print("TEST ARRAY ADD TO SELF: " + str(len(y))) + x.clear() + + x = Array() + y = Array() + x.push_back(y) + print("TEST ARRAY ADD TO SELF: " + str(len(y))) + x.clear() diff --git a/modules/gdscript/tests/scripts/runtime/features/arrays_arent_shared.out b/modules/gdscript/tests/scripts/runtime/features/arrays_arent_shared.out new file mode 100644 index 0000000000..f6b7d3cc39 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/arrays_arent_shared.out @@ -0,0 +1,6 @@ +GDTEST_OK +TEST ARRAY ADD TO SELF: 0 +TEST ARRAY ADD TO SELF: 0 +TEST ARRAY ADD TO SELF: 0 +TEST ARRAY ADD TO SELF: 0 +TEST ARRAY ADD TO SELF: 0 diff --git a/modules/gdscript/tests/scripts/runtime/features/assign_member_with_operation.gd b/modules/gdscript/tests/scripts/runtime/features/assign_member_with_operation.gd new file mode 100644 index 0000000000..f6526aefb4 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/assign_member_with_operation.gd @@ -0,0 +1,13 @@ +extends Node + +func test(): + process_priority = 10 + var change = 20 + + print(process_priority) + print(change) + + process_priority += change + + print(process_priority) + print(change) diff --git a/modules/gdscript/tests/scripts/runtime/features/assign_member_with_operation.out b/modules/gdscript/tests/scripts/runtime/features/assign_member_with_operation.out new file mode 100644 index 0000000000..c9e6b34c77 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/assign_member_with_operation.out @@ -0,0 +1,5 @@ +GDTEST_OK +10 +20 +30 +20 diff --git a/modules/gdscript/tests/scripts/runtime/features/await_without_coroutine.gd b/modules/gdscript/tests/scripts/runtime/features/await_without_coroutine.gd new file mode 100644 index 0000000000..9da61ab184 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/await_without_coroutine.gd @@ -0,0 +1,8 @@ +# https://github.com/godotengine/godot/issues/50894 + +func test(): + print(await not_coroutine()) + + +func not_coroutine(): + return "awaited" diff --git a/modules/gdscript/tests/scripts/runtime/features/await_without_coroutine.out b/modules/gdscript/tests/scripts/runtime/features/await_without_coroutine.out new file mode 100644 index 0000000000..c2ac488e9b --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/await_without_coroutine.out @@ -0,0 +1,6 @@ +GDTEST_OK +>> WARNING +>> Line: 4 +>> REDUNDANT_AWAIT +>> "await" keyword not needed in this case, because the expression isn't a coroutine nor a signal. +awaited diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd new file mode 100644 index 0000000000..c6645c2c34 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd @@ -0,0 +1,138 @@ +func test(): + var value + + # null + value = null + print(value == null) + + # bool + value = false + print(value == null) + + # int + value = 0 + print(value == null) + + # float + value = 0.0 + print(value == null) + + # String + value = "" + print(value == null) + + # Vector2 + value = Vector2() + print(value == null) + + # Vector2i + value = Vector2i() + print(value == null) + + # Rect2 + value = Rect2() + print(value == null) + + # Rect2i + value = Rect2i() + print(value == null) + + # Vector3 + value = Vector3() + print(value == null) + + # Vector3i + value = Vector3i() + print(value == null) + + # Transform2D + value = Transform2D() + print(value == null) + + # Plane + value = Plane() + print(value == null) + + # Quaternion + value = Quaternion() + print(value == null) + + # AABB + value = AABB() + print(value == null) + + # Basis + value = Basis() + print(value == null) + + # Transform3D + value = Transform3D() + print(value == null) + + # Color + value = Color() + print(value == null) + + # StringName + value = &"" + print(value == null) + + # NodePath + value = ^"" + print(value == null) + + # RID + value = RID() + print(value == null) + + # Callable + value = Callable() + print(value == null) + + # Signal + value = Signal() + print(value == null) + + # Dictionary + value = {} + print(value == null) + + # Array + value = [] + print(value == null) + + # PackedByteArray + value = PackedByteArray() + print(value == null) + + # PackedInt32Array + value = PackedInt32Array() + print(value == null) + + # PackedInt64Array + value = PackedInt64Array() + print(value == null) + + # PackedFloat32Array + value = PackedFloat32Array() + print(value == null) + + # PackedFloat64Array + value = PackedFloat64Array() + print(value == null) + + # PackedStringArray + value = PackedStringArray() + print(value == null) + + # PackedVector2Array + value = PackedVector2Array() + print(value == null) + + # PackedVector3Array + value = PackedVector3Array() + print(value == null) + + # PackedColorArray + value = PackedColorArray() + print(value == null) diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out new file mode 100644 index 0000000000..639f6027b9 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out @@ -0,0 +1,35 @@ +GDTEST_OK +true +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd new file mode 100644 index 0000000000..ee622bf22f --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd @@ -0,0 +1,138 @@ +func test(): + var value + + # null + value = null + print(value != null) + + # bool + value = false + print(value != null) + + # int + value = 0 + print(value != null) + + # float + value = 0.0 + print(value != null) + + # String + value = "" + print(value != null) + + # Vector2 + value = Vector2() + print(value != null) + + # Vector2i + value = Vector2i() + print(value != null) + + # Rect2 + value = Rect2() + print(value != null) + + # Rect2i + value = Rect2i() + print(value != null) + + # Vector3 + value = Vector3() + print(value != null) + + # Vector3i + value = Vector3i() + print(value != null) + + # Transform2D + value = Transform2D() + print(value != null) + + # Plane + value = Plane() + print(value != null) + + # Quaternion + value = Quaternion() + print(value != null) + + # AABB + value = AABB() + print(value != null) + + # Basis + value = Basis() + print(value != null) + + # Transform3D + value = Transform3D() + print(value != null) + + # Color + value = Color() + print(value != null) + + # StringName + value = &"" + print(value != null) + + # NodePath + value = ^"" + print(value != null) + + # RID + value = RID() + print(value != null) + + # Callable + value = Callable() + print(value != null) + + # Signal + value = Signal() + print(value != null) + + # Dictionary + value = {} + print(value != null) + + # Array + value = [] + print(value != null) + + # PackedByteArray + value = PackedByteArray() + print(value != null) + + # PackedInt32Array + value = PackedInt32Array() + print(value != null) + + # PackedInt64Array + value = PackedInt64Array() + print(value != null) + + # PackedFloat32Array + value = PackedFloat32Array() + print(value != null) + + # PackedFloat64Array + value = PackedFloat64Array() + print(value != null) + + # PackedStringArray + value = PackedStringArray() + print(value != null) + + # PackedVector2Array + value = PackedVector2Array() + print(value != null) + + # PackedVector3Array + value = PackedVector3Array() + print(value != null) + + # PackedColorArray + value = PackedColorArray() + print(value != null) diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out new file mode 100644 index 0000000000..d1e332afba --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out @@ -0,0 +1,35 @@ +GDTEST_OK +false +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.gd b/modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.gd new file mode 100644 index 0000000000..7649062fda --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.gd @@ -0,0 +1,138 @@ +func test(): + var value + + # null + value = null + print(null == value) + + # bool + value = false + print(null == value) + + # int + value = 0 + print(null == value) + + # float + value = 0.0 + print(null == value) + + # String + value = "" + print(null == value) + + # Vector2 + value = Vector2() + print(null == value) + + # Vector2i + value = Vector2i() + print(null == value) + + # Rect2 + value = Rect2() + print(null == value) + + # Rect2i + value = Rect2i() + print(null == value) + + # Vector3 + value = Vector3() + print(null == value) + + # Vector3i + value = Vector3i() + print(null == value) + + # Transform2D + value = Transform2D() + print(null == value) + + # Plane + value = Plane() + print(null == value) + + # Quaternion + value = Quaternion() + print(null == value) + + # AABB + value = AABB() + print(null == value) + + # Basis + value = Basis() + print(null == value) + + # Transform3D + value = Transform3D() + print(null == value) + + # Color + value = Color() + print(null == value) + + # StringName + value = &"" + print(null == value) + + # NodePath + value = ^"" + print(null == value) + + # RID + value = RID() + print(null == value) + + # Callable + value = Callable() + print(null == value) + + # Signal + value = Signal() + print(null == value) + + # Dictionary + value = {} + print(null == value) + + # Array + value = [] + print(null == value) + + # PackedByteArray + value = PackedByteArray() + print(null == value) + + # PackedInt32Array + value = PackedInt32Array() + print(null == value) + + # PackedInt64Array + value = PackedInt64Array() + print(null == value) + + # PackedFloat32Array + value = PackedFloat32Array() + print(null == value) + + # PackedFloat64Array + value = PackedFloat64Array() + print(null == value) + + # PackedStringArray + value = PackedStringArray() + print(null == value) + + # PackedVector2Array + value = PackedVector2Array() + print(null == value) + + # PackedVector3Array + value = PackedVector3Array() + print(null == value) + + # PackedColorArray + value = PackedColorArray() + print(null == value) diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.out b/modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.out new file mode 100644 index 0000000000..639f6027b9 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/compare-null-equals-builtin.out @@ -0,0 +1,35 @@ +GDTEST_OK +true +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false +false diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.gd b/modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.gd new file mode 100644 index 0000000000..8d5f9df1b8 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.gd @@ -0,0 +1,138 @@ +func test(): + var value + + # null + value = null + print(null != value) + + # bool + value = false + print(null != value) + + # int + value = 0 + print(null != value) + + # float + value = 0.0 + print(null != value) + + # String + value = "" + print(null != value) + + # Vector2 + value = Vector2() + print(null != value) + + # Vector2i + value = Vector2i() + print(null != value) + + # Rect2 + value = Rect2() + print(null != value) + + # Rect2i + value = Rect2i() + print(null != value) + + # Vector3 + value = Vector3() + print(null != value) + + # Vector3i + value = Vector3i() + print(null != value) + + # Transform2D + value = Transform2D() + print(null != value) + + # Plane + value = Plane() + print(null != value) + + # Quaternion + value = Quaternion() + print(null != value) + + # AABB + value = AABB() + print(null != value) + + # Basis + value = Basis() + print(null != value) + + # Transform3D + value = Transform3D() + print(null != value) + + # Color + value = Color() + print(null != value) + + # StringName + value = &"" + print(null != value) + + # NodePath + value = ^"" + print(null != value) + + # RID + value = RID() + print(null != value) + + # Callable + value = Callable() + print(null != value) + + # Signal + value = Signal() + print(null != value) + + # Dictionary + value = {} + print(null != value) + + # Array + value = [] + print(null != value) + + # PackedByteArray + value = PackedByteArray() + print(null != value) + + # PackedInt32Array + value = PackedInt32Array() + print(null != value) + + # PackedInt64Array + value = PackedInt64Array() + print(null != value) + + # PackedFloat32Array + value = PackedFloat32Array() + print(null != value) + + # PackedFloat64Array + value = PackedFloat64Array() + print(null != value) + + # PackedStringArray + value = PackedStringArray() + print(null != value) + + # PackedVector2Array + value = PackedVector2Array() + print(null != value) + + # PackedVector3Array + value = PackedVector3Array() + print(null != value) + + # PackedColorArray + value = PackedColorArray() + print(null != value) diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.out b/modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.out new file mode 100644 index 0000000000..d1e332afba --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/compare-null-not-equals-builtin.out @@ -0,0 +1,35 @@ +GDTEST_OK +false +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true +true diff --git a/modules/gdscript/tests/scripts/runtime/features/dictionaries_arent_shared.gd b/modules/gdscript/tests/scripts/runtime/features/dictionaries_arent_shared.gd new file mode 100644 index 0000000000..d5a5f8de64 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/dictionaries_arent_shared.gd @@ -0,0 +1,19 @@ +# https://github.com/godotengine/godot/issues/48121 + +func test(): + var x := Dictionary() + var y := Dictionary() + y[0]=1 + y[1]=1 + y[2]=1 + print("TEST OTHER DICTIONARY: " + str(len(x))) + x.clear() + + x = Dictionary().duplicate() + y = Dictionary().duplicate() + y[0]=1 + y[1]=1 + y[2]=1 + print("TEST OTHER DICTIONARY: " + str(len(x))) + x.clear() + return diff --git a/modules/gdscript/tests/scripts/runtime/features/dictionaries_arent_shared.out b/modules/gdscript/tests/scripts/runtime/features/dictionaries_arent_shared.out new file mode 100644 index 0000000000..0bf49f5934 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/dictionaries_arent_shared.out @@ -0,0 +1,3 @@ +GDTEST_OK +TEST OTHER DICTIONARY: 0 +TEST OTHER DICTIONARY: 0 diff --git a/modules/gdscript/tests/scripts/runtime/features/lua_assign.gd b/modules/gdscript/tests/scripts/runtime/features/lua_assign.gd new file mode 100644 index 0000000000..c9b5f8481e --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/lua_assign.gd @@ -0,0 +1,4 @@ +func test(): + var dict = {} + dict.test = 1 + print(dict.test) diff --git a/modules/gdscript/tests/scripts/runtime/features/lua_assign.out b/modules/gdscript/tests/scripts/runtime/features/lua_assign.out new file mode 100644 index 0000000000..a7f1357bb2 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/lua_assign.out @@ -0,0 +1,2 @@ +GDTEST_OK +1 diff --git a/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.gd b/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.gd new file mode 100644 index 0000000000..3eb02816ed --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.gd @@ -0,0 +1,11 @@ +#GDTEST_OK +var prop : int = 0: + get: + return prop + set(value): + prop = value % 7 + +func test(): + for i in 7: + prop += 1 + print(prop) diff --git a/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.out b/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.out new file mode 100644 index 0000000000..76157853f2 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/property_with_operator_assignment.out @@ -0,0 +1,8 @@ +GDTEST_OK +1 +2 +3 +4 +5 +6 +0 diff --git a/modules/gdscript/tests/scripts/runtime/features/recursion.gd b/modules/gdscript/tests/scripts/runtime/features/recursion.gd new file mode 100644 index 0000000000..a35485022e --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/recursion.gd @@ -0,0 +1,19 @@ +func is_prime(number: int, divisor: int = 2) -> bool: + print(divisor) + if number <= 2: + return (number == 2) + elif number % divisor == 0: + return false + elif divisor * divisor > number: + return true + + return is_prime(number, divisor + 1) + +func test(): + # Not a prime number. + print(is_prime(989)) + + print() + + # Largest prime number below 10000. + print(is_prime(9973)) diff --git a/modules/gdscript/tests/scripts/runtime/features/recursion.out b/modules/gdscript/tests/scripts/runtime/features/recursion.out new file mode 100644 index 0000000000..2bd8f24988 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/recursion.out @@ -0,0 +1,125 @@ +GDTEST_OK +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +false + +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +true diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.gd b/modules/gdscript/tests/scripts/runtime/features/stringify.gd new file mode 100644 index 0000000000..fead2df854 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/stringify.gd @@ -0,0 +1,42 @@ +func test(): + print(true, false) + print(-1, 0, 1) + print(-1.25, 0.25, 1.25) + print("hello world") + + print(Vector2(0.25, 0.25)) + print(Vector2i(0, 0)) + + print(Rect2(0.25, 0.25, 0.5, 0.5)) + print(Rect2i(0, 0, 0, 0)) + + print(Vector3(0.25, 0.25, 0.25)) + print(Vector3i(0, 0, 0)) + + print(Transform2D.IDENTITY) + print(Plane(1, 2, 3, 4)) + print(Quaternion(1, 2, 3, 4)) + print(AABB(Vector3.ZERO, Vector3.ONE)) + print(Basis.from_euler(Vector3(0, 0, 0))) + print(Transform3D.IDENTITY) + + print(Color(1, 2, 3, 4)) + print(StringName("hello")) + print(NodePath("hello/world")) + var node := Node.new() + print(RID(node)) + print(node.get_name) + print(node.property_list_changed) + node.free() + print({"hello":123}) + print(["hello", 123]) + + print(PackedByteArray([-1, 0, 1])) + print(PackedInt32Array([-1, 0, 1])) + print(PackedInt64Array([-1, 0, 1])) + print(PackedFloat32Array([-1, 0, 1])) + print(PackedFloat64Array([-1, 0, 1])) + print(PackedStringArray(["hello", "world"])) + print(PackedVector2Array([Vector2.ONE, Vector2.ZERO])) + print(PackedVector3Array([Vector3.ONE, Vector3.ZERO])) + print(PackedColorArray([Color.RED, Color.BLUE, Color.GREEN])) diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.out b/modules/gdscript/tests/scripts/runtime/features/stringify.out new file mode 100644 index 0000000000..7670fc0128 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/stringify.out @@ -0,0 +1,34 @@ +GDTEST_OK +truefalse +-101 +-1.250.251.25 +hello world +(0.25, 0.25) +(0, 0) +[P: (0.25, 0.25), S: (0.5, 0.5)] +[P: (0, 0), S: (0, 0)] +(0.25, 0.25, 0.25) +(0, 0, 0) +[X: (1, 0), Y: (0, 1), O: (0, 0)] +[N: (1, 2, 3), D: 4] +(1, 2, 3, 4) +[P: (0, 0, 0), S: (1, 1, 1)] +[X: (1, 0, 0), Y: (0, 1, 0), Z: (0, 0, 1)] +[X: (1, 0, 0), Y: (0, 1, 0), Z: (0, 0, 1), O: (0, 0, 0)] +(1, 2, 3, 4) +hello +hello/world +RID(0) +Node::get_name +Node::[signal]property_list_changed +{hello:123} +[hello, 123] +[255, 0, 1] +[-1, 0, 1] +[-1, 0, 1] +[-1, 0, 1] +[-1, 0, 1] +[hello, world] +[(1, 1), (0, 0)] +[(1, 1, 1), (0, 0, 0)] +[(1, 0, 0, 1), (0, 0, 1, 1), (0, 1, 0, 1)] diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp index 36da64bbaa..80eabc1596 100644 --- a/modules/gdscript/tests/test_gdscript.cpp +++ b/modules/gdscript/tests/test_gdscript.cpp @@ -31,8 +31,8 @@ #include "test_gdscript.h" #include "core/config/project_settings.h" +#include "core/io/file_access.h" #include "core/io/file_access_pack.h" -#include "core/os/file_access.h" #include "core/os/main_loop.h" #include "core/os/os.h" #include "core/string/string_builder.h" @@ -56,7 +56,7 @@ static void test_tokenizer(const String &p_code, const Vector<String> &p_lines) int tab_size = 4; #ifdef TOOLS_ENABLED if (EditorSettings::get_singleton()) { - tab_size = EditorSettings::get_singleton()->get_setting("text_editor/indent/size"); + tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size"); } #endif // TOOLS_ENABLED String tab = String(" ").repeat(tab_size); @@ -113,8 +113,7 @@ static void test_parser(const String &p_code, const String &p_script_path, const if (err != OK) { const List<GDScriptParser::ParserError> &errors = parser.get_errors(); - for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) { - const GDScriptParser::ParserError &error = E->get(); + for (const GDScriptParser::ParserError &error : errors) { print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message)); } } @@ -124,8 +123,7 @@ static void test_parser(const String &p_code, const String &p_script_path, const if (err != OK) { const List<GDScriptParser::ParserError> &errors = parser.get_errors(); - for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) { - const GDScriptParser::ParserError &error = E->get(); + for (const GDScriptParser::ParserError &error : errors) { print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message)); } } @@ -143,8 +141,7 @@ static void test_compiler(const String &p_code, const String &p_script_path, con if (err != OK) { print_line("Error in parser:"); const List<GDScriptParser::ParserError> &errors = parser.get_errors(); - for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) { - const GDScriptParser::ParserError &error = E->get(); + for (const GDScriptParser::ParserError &error : errors) { print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message)); } return; @@ -156,8 +153,7 @@ static void test_compiler(const String &p_code, const String &p_script_path, con if (err != OK) { print_line("Error in analyzer:"); const List<GDScriptParser::ParserError> &errors = parser.get_errors(); - for (const List<GDScriptParser::ParserError>::Element *E = errors.front(); E != nullptr; E = E->next()) { - const GDScriptParser::ParserError &error = E->get(); + for (const GDScriptParser::ParserError &error : errors) { print_line(vformat("%02d:%02d: %s", error.line, error.column, error.message)); } return; @@ -165,7 +161,7 @@ static void test_compiler(const String &p_code, const String &p_script_path, con GDScriptCompiler compiler; Ref<GDScript> script; - script.instance(); + script.instantiate(); script->set_path(p_script_path); err = compiler.compile(&parser, script.ptr(), false); @@ -176,8 +172,8 @@ static void test_compiler(const String &p_code, const String &p_script_path, con return; } - for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) { - const GDScriptFunction *func = E->value(); + 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++) { @@ -215,8 +211,8 @@ void test(TestType p_type) { init_language(fa->get_path_absolute().get_base_dir()); Vector<uint8_t> buf; - int flen = fa->get_len(); - buf.resize(fa->get_len() + 1); + uint64_t flen = fa->get_length(); + buf.resize(flen + 1); fa->get_buffer(buf.ptrw(), flen); buf.write[flen] = 0; |