diff options
Diffstat (limited to 'modules/gdscript')
364 files changed, 7316 insertions, 3936 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index c8eda53a2d..5bed1b9da3 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -106,6 +106,7 @@ <param index="0" name="instance" type="Object" /> <description> Returns the passed [param instance] converted to a Dictionary. Can be useful for serializing. + [b]Note:[/b] Cannot be used to serialize objects with built-in scripts attached or objects allocated within built-in scripts. [codeblock] var foo = "bar" func _ready(): @@ -484,7 +485,7 @@ Export a [NodePath] property with a filter for allowed node types. See also [constant PROPERTY_HINT_NODE_PATH_VALID_TYPES]. [codeblock] - @export_node_path(Button, TouchScreenButton) var some_button + @export_node_path("Button", "TouchScreenButton") var some_button [/codeblock] </description> </annotation> @@ -547,11 +548,12 @@ <return type="void" /> <param index="0" name="icon_path" type="String" /> <description> - Add a custom icon to the current script. After loading an icon at [param icon_path], the icon is displayed in the Scene dock for every node that the script is attached to. For named classes, the icon is also displayed in various editor dialogs. + Add a custom icon to the current script. The script must be registered as a global class using the [code]class_name[/code] keyword for this to have a visible effect. The icon specified at [param icon_path] is displayed in the Scene dock for every node of that class, as well as in various editor dialogs. [codeblock] @icon("res://path/to/class/icon.svg") [/codeblock] [b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported. + [b]Note:[/b] As annotations describe their subject, the [code]@icon[/code] annotation must be placed before the class definition and inheritance. </description> </annotation> <annotation name="@onready"> @@ -584,6 +586,7 @@ @tool extends Node [/codeblock] + [b]Note:[/b] As annotations describe their subject, the [code]@tool[/code] annotation must be placed before the class definition and inheritance. </description> </annotation> <annotation name="@warning_ignore" qualifiers="vararg"> diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml index 8246c96c15..1a102bd16f 100644 --- a/modules/gdscript/doc_classes/GDScript.xml +++ b/modules/gdscript/doc_classes/GDScript.xml @@ -11,12 +11,6 @@ <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" /> - <description> - Returns byte code for the script source code. - </description> - </method> <method name="new" qualifiers="vararg"> <return type="Variant" /> <description> diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 996d323a7f..23be913a24 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_highlighter.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_highlighter.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_highlighter.h" #include "../gdscript.h" @@ -115,10 +115,10 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } if (from != line_length) { - /* check if we are in entering a region */ + // Check if we are in entering a region. if (in_region == -1) { for (int c = 0; c < color_regions.size(); c++) { - /* check there is enough room */ + // Check there is enough room. int chars_left = line_length - from; int start_key_length = color_regions[c].start_key.length(); int end_key_length = color_regions[c].end_key.length(); @@ -126,7 +126,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l continue; } - /* search the line */ + // Search the line. bool match = true; const char32_t *start_key = color_regions[c].start_key.get_data(); for (int k = 0; k < start_key_length; k++) { @@ -141,7 +141,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l in_region = c; from += start_key_length; - /* check if it's the whole line */ + // Check if it's the whole line. if (end_key_length == 0 || color_regions[c].line_only || from + end_key_length > line_length) { if (from + end_key_length > line_length) { // If it's key length and there is a '\', dont skip to highlight esc chars. @@ -166,7 +166,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } } - /* if we are in one find the end key */ + // If we are in one, find the end key. if (in_region != -1) { Color region_color = color_regions[in_region].color; if (in_node_path && (color_regions[in_region].start_key == "\"" || color_regions[in_region].start_key == "\'")) { @@ -183,7 +183,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l highlighter_info["color"] = region_color; color_map[j] = highlighter_info; - /* search the line */ + // Search the line. int region_end_index = -1; int end_key_length = color_regions[in_region].end_key.length(); const char32_t *end_key = color_regions[in_region].end_key.get_data(); @@ -267,14 +267,14 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l in_keyword = false; } - // allow ABCDEF in hex notation + // Allow ABCDEF in hex notation. if (is_hex_notation && (is_hex_digit(str[j]) || is_a_digit)) { is_a_digit = true; } else { is_hex_notation = false; } - // disallow anything not a 0 or 1 in binary notation + // Disallow anything not a 0 or 1 in binary notation. if (is_bin_notation && !is_binary_digit(str[j])) { is_a_digit = false; is_bin_notation = false; @@ -284,7 +284,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l in_number = true; } - // Special cases for numbers + // Special cases for numbers. if (in_number && !is_a_digit) { if (str[j] == 'b' && str[j - 1] == '0') { is_bin_notation = true; @@ -322,7 +322,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } String word = str.substr(j, to - j); - Color col = Color(); + Color col; if (global_functions.has(word)) { // "assert" and "preload" are reserved, so highlight even if not followed by a bracket. if (word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::ASSERT) || word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::PRELOAD)) { @@ -349,7 +349,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l if (col != Color()) { for (int k = j - 1; k >= 0; k--) { if (str[k] == '.') { - col = Color(); // keyword, member & global func indexing not allowed + col = Color(); // Keyword, member & global func indexing not allowed. break; } else if (str[k] > 32) { break; @@ -372,7 +372,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l k++; } - // check for space between name and bracket + // Check for space between name and bracket. while (k < line_length && is_whitespace(str[k])) { k++; } @@ -445,7 +445,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l in_member_variable = false; } - // Keep symbol color for binary '&&'. In the case of '&&&' use StringName color for the last ampersand + // Keep symbol color for binary '&&'. In the case of '&&&' use StringName color for the last ampersand. if (!in_string_name && in_region == -1 && str[j] == '&' && !is_binary_op) { if (j >= 2 && str[j - 1] == '&' && str[j - 2] != '&' && prev_is_binary_op) { is_binary_op = true; @@ -456,7 +456,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l in_string_name = false; } - // '^^' has no special meaning, so unlike StringName, when binary, use NodePath color for the last caret + // '^^' has no special meaning, so unlike StringName, when binary, use NodePath color for the last caret. if (!in_node_path && in_region == -1 && str[j] == '^' && !is_binary_op && (j == 0 || (j > 0 && str[j - 1] != '^') || prev_is_binary_op)) { in_node_path = true; } else if (in_region != -1 || is_a_symbol) { @@ -465,7 +465,8 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l if (!in_node_ref && in_region == -1 && (str[j] == '$' || (str[j] == '%' && !is_binary_op))) { in_node_ref = true; - } else if (in_region != -1 || (is_a_symbol && str[j] != '/' && str[j] != '%')) { + } else if (in_region != -1 || (is_a_symbol && str[j] != '/' && str[j] != '%') || (is_a_digit && j > 0 && (str[j - 1] == '$' || str[j - 1] == '/' || str[j - 1] == '%'))) { + // NodeRefs can't start with digits, so point out wrong syntax immediately. in_node_ref = false; } @@ -525,7 +526,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l prev_type = current_type; current_type = next_type; - // no need to store regions... + // No need to store regions... if (prev_type == REGION) { prev_text = ""; prev_column = j; @@ -533,7 +534,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l String text = str.substr(prev_column, j - prev_column).strip_edges(); prev_column = j; - // ignore if just whitespace + // Ignore if just whitespace. if (!text.is_empty()) { prev_text = text; } @@ -683,7 +684,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { } } - const String text_edit_color_theme = EditorSettings::get_singleton()->get("text_editor/theme/color_theme"); + const String text_edit_color_theme = EDITOR_GET("text_editor/theme/color_theme"); const bool godot_2_theme = text_edit_color_theme == "Godot 2"; if (godot_2_theme || EditorSettings::get_singleton()->is_dark_theme()) { diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h index 7c22eb30b1..aceb644658 100644 --- a/modules/gdscript/editor/gdscript_highlighter.h +++ b/modules/gdscript/editor/gdscript_highlighter.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_highlighter.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_highlighter.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_HIGHLIGHTER_H #define GDSCRIPT_HIGHLIGHTER_H diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp index 518d4bcb62..00d50d1737 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_translation_parser_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_translation_parser_plugin.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_translation_parser_plugin.h" diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.h b/modules/gdscript/editor/gdscript_translation_parser_plugin.h index 969a50f48c..7e6e381e3f 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.h +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_translation_parser_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_translation_parser_plugin.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H #define GDSCRIPT_TRANSLATION_PARSER_PLUGIN_H diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index b4da9c1224..4fc3929bbd 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript.h" @@ -248,6 +248,10 @@ Ref<Script> GDScript::get_base_script() const { } } +StringName GDScript::get_global_name() const { + return name; +} + StringName GDScript::get_instance_base_type() const { if (native.is_valid()) { return native->get_name(); @@ -478,7 +482,7 @@ void GDScript::_clear_doc() { void GDScript::_update_doc() { _clear_doc(); - doc.script_path = "\"" + get_path().get_slice("://", 1) + "\""; + doc.script_path = vformat(R"("%s")", get_script_path().get_slice("://", 1)); if (!name.is_empty()) { doc.name = name; } else { @@ -674,36 +678,14 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc base_cache = Ref<GDScript>(); } - if (c->extends_used) { - String ext_path = ""; - if (String(c->extends_path) != "" && String(c->extends_path) != get_path()) { - ext_path = c->extends_path; - if (ext_path.is_relative_path()) { - String base_path = get_path(); - if (base_path.is_empty() || base_path.is_relative_path()) { - ERR_PRINT(("Could not resolve relative path for parent class: " + ext_path).utf8().get_data()); - } else { - ext_path = base_path.get_base_dir().path_join(ext_path); - } - } - } else if (c->extends.size() != 0) { - const StringName &base_class = c->extends[0]; - - if (ScriptServer::is_global_class(base_class)) { - ext_path = ScriptServer::get_global_class_path(base_class); - } - } - - if (!ext_path.is_empty()) { - if (ext_path != get_path()) { - Ref<GDScript> bf = ResourceLoader::load(ext_path); - - if (bf.is_valid()) { - base_cache = bf; - bf->inheriters_cache.insert(get_instance_id()); - } - } else { - ERR_PRINT(("Path extending itself in " + ext_path).utf8().get_data()); + GDScriptParser::DataType base_type = parser.get_tree()->base_type; + if (base_type.kind == GDScriptParser::DataType::CLASS) { + Ref<GDScript> bf = GDScriptCache::get_full_script(base_type.script_path, err, path); + if (err == OK) { + bf = Ref<GDScript>(bf->find_class(base_type.class_type->fqcn)); + if (bf.is_valid()) { + base_cache = bf; + bf->inheriters_cache.insert(get_instance_id()); } } } @@ -727,6 +709,7 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc Variant default_value; if (member.variable->initializer && member.variable->initializer->is_constant) { default_value = member.variable->initializer->reduced_value; + GDScriptCompiler::convert_to_initializer_type(default_value, member.variable); } member_default_values_cache[member.variable->identifier->name] = default_value; } break; @@ -825,22 +808,20 @@ void GDScript::update_exports() { #endif } -void GDScript::_set_subclass_path(Ref<GDScript> &p_sc, const String &p_path) { - p_sc->path = 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() + ")"; + return vformat("%s(%s)", get_name(), get_script_path()); } else { - return get_path(); + return get_script_path(); } } Error GDScript::reload(bool p_keep_state) { + if (reloading) { + return OK; + } + reloading = true; + bool has_instances; { MutexLock lock(GDScriptLanguage::singleton->mutex); @@ -860,9 +841,10 @@ Error GDScript::reload(bool p_keep_state) { basedir = basedir.get_base_dir(); } -// Loading a template, don't parse. + // Loading a template, don't parse. #ifdef TOOLS_ENABLED if (EditorPaths::get_singleton() && basedir.begins_with(EditorPaths::get_singleton()->get_project_script_templates_dir())) { + reloading = false; return OK; } #endif @@ -872,11 +854,10 @@ Error GDScript::reload(bool p_keep_state) { if (source_path.is_empty()) { source_path = get_path(); } - if (!source_path.is_empty()) { - MutexLock lock(GDScriptCache::singleton->lock); - if (!GDScriptCache::singleton->shallow_gdscript_cache.has(source_path)) { - GDScriptCache::singleton->shallow_gdscript_cache[source_path] = this; - } + Ref<GDScript> cached_script = GDScriptCache::get_cached_script(source_path); + if (!source_path.is_empty() && cached_script.is_null()) { + MutexLock lock(GDScriptCache::singleton->mutex); + GDScriptCache::singleton->shallow_gdscript_cache[source_path] = Ref<GDScript>(this); } } @@ -889,6 +870,7 @@ Error GDScript::reload(bool p_keep_state) { } // 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(), false, ERR_HANDLER_SCRIPT); + reloading = false; return ERR_PARSE_ERROR; } @@ -905,6 +887,7 @@ Error GDScript::reload(bool p_keep_state) { _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(); } + reloading = false; return ERR_PARSE_ERROR; } @@ -913,18 +896,16 @@ Error GDScript::reload(bool p_keep_state) { GDScriptCompiler compiler; err = compiler.compile(&parser, this, p_keep_state); -#ifdef TOOLS_ENABLED - _update_doc(); -#endif - if (err) { if (can_run) { if (EngineDebugger::is_active()) { 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(), false, ERR_HANDLER_SCRIPT); + reloading = false; return ERR_COMPILATION_FAILED; } else { + reloading = false; return err; } } @@ -932,19 +913,12 @@ Error GDScript::reload(bool p_keep_state) { 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(), false, ERR_HANDLER_WARNING, si); + EngineDebugger::get_script_debugger()->send_error("", get_script_path(), warning.start_line, warning.get_name(), warning.get_message(), false, ERR_HANDLER_WARNING, si); } } #endif - valid = true; - - for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { - _set_subclass_path(E.value, path); - } - - _init_rpc_methods_properties(); - + reloading = false; return OK; } @@ -1037,25 +1011,29 @@ void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const { void GDScript::_bind_methods() { ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &GDScript::_new, MethodInfo("new")); - - ClassDB::bind_method(D_METHOD("get_as_byte_code"), &GDScript::get_as_byte_code); -} - -Vector<uint8_t> GDScript::get_as_byte_code() const { - return Vector<uint8_t>(); -}; - -// TODO: Fully remove this. There's not this kind of "bytecode" anymore. -Error GDScript::load_byte_code(const String &p_path) { - return ERR_COMPILATION_FAILED; } void GDScript::set_path(const String &p_path, bool p_take_over) { - Script::set_path(p_path, p_take_over); + String old_path = path; + if (is_root_script()) { + Script::set_path(p_path, p_take_over); + } this->path = p_path; + GDScriptCache::move_script(old_path, p_path); + for (KeyValue<StringName, Ref<GDScript>> &kv : subclasses) { + kv.value->set_path(p_path, p_take_over); + } +} + +String GDScript::get_script_path() const { + return path; } Error GDScript::load_source_code(const String &p_path) { + if (p_path.is_empty() || p_path.begins_with("gdscript://") || ResourceLoader::get_resource_type(p_path.get_slice("::", 0)) == "PackedScene") { + return OK; + } + Vector<uint8_t> sourcef; Error err; Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err); @@ -1127,6 +1105,131 @@ bool GDScript::inherits_script(const Ref<Script> &p_script) const { return false; } +GDScript *GDScript::find_class(const String &p_qualified_name) { + String first = p_qualified_name.get_slice("::", 0); + + Vector<String> class_names; + GDScript *result = nullptr; + // Empty initial name means start here. + if (first.is_empty() || first == name) { + class_names = p_qualified_name.split("::"); + result = this; + } else if (p_qualified_name.begins_with(get_root_script()->path)) { + // Script path could have a class path separator("::") in it. + class_names = p_qualified_name.trim_prefix(get_root_script()->path).split("::"); + result = get_root_script(); + } else if (HashMap<StringName, Ref<GDScript>>::Iterator E = subclasses.find(first)) { + class_names = p_qualified_name.split("::"); + result = E->value.ptr(); + } else if (_owner != nullptr) { + // Check parent scope. + return _owner->find_class(p_qualified_name); + } + + // Starts at index 1 because index 0 was handled above. + for (int i = 1; result != nullptr && i < class_names.size(); i++) { + String current_name = class_names[i]; + if (HashMap<StringName, Ref<GDScript>>::Iterator E = result->subclasses.find(current_name)) { + result = E->value.ptr(); + } else { + // Couldn't find inner class. + return nullptr; + } + } + + return result; +} + +bool GDScript::has_class(const GDScript *p_script) { + String fqn = p_script->fully_qualified_name; + if (fully_qualified_name.is_empty() && fqn.get_slice("::", 0).is_empty()) { + return p_script == this; + } else if (fqn.begins_with(fully_qualified_name)) { + return p_script == find_class(fqn.trim_prefix(fully_qualified_name)); + } + return false; +} + +GDScript *GDScript::get_root_script() { + GDScript *result = this; + while (result->_owner) { + result = result->_owner; + } + return result; +} + +RBSet<GDScript *> GDScript::get_dependencies() { + RBSet<GDScript *> dependencies; + + _get_dependencies(dependencies, this); + dependencies.erase(this); + + return dependencies; +} + +RBSet<GDScript *> GDScript::get_inverted_dependencies() { + RBSet<GDScript *> inverted_dependencies; + + List<GDScript *> scripts; + { + MutexLock lock(GDScriptLanguage::singleton->mutex); + + SelfList<GDScript> *elem = GDScriptLanguage::singleton->script_list.first(); + while (elem) { + scripts.push_back(elem->self()); + elem = elem->next(); + } + } + + for (GDScript *scr : scripts) { + if (scr == nullptr || scr == this || scr->destructing) { + continue; + } + + RBSet<GDScript *> scr_dependencies = scr->get_dependencies(); + if (scr_dependencies.has(this)) { + inverted_dependencies.insert(scr); + } + } + + return inverted_dependencies; +} + +RBSet<GDScript *> GDScript::get_must_clear_dependencies() { + RBSet<GDScript *> dependencies = get_dependencies(); + RBSet<GDScript *> must_clear_dependencies; + HashMap<GDScript *, RBSet<GDScript *>> inverted_dependencies; + + for (GDScript *E : dependencies) { + inverted_dependencies.insert(E, E->get_inverted_dependencies()); + } + + RBSet<GDScript *> cant_clear; + for (KeyValue<GDScript *, RBSet<GDScript *>> &E : inverted_dependencies) { + for (GDScript *F : E.value) { + if (!dependencies.has(F)) { + cant_clear.insert(E.key); + for (GDScript *G : E.key->get_dependencies()) { + cant_clear.insert(G); + } + break; + } + } + } + + for (KeyValue<GDScript *, RBSet<GDScript *>> &E : inverted_dependencies) { + if (cant_clear.has(E.key) || ScriptServer::is_global_class(E.key->get_fully_qualified_name())) { + continue; + } + must_clear_dependencies.insert(E.key); + } + + cant_clear.clear(); + dependencies.clear(); + inverted_dependencies.clear(); + return must_clear_dependencies; +} + bool GDScript::has_script_signal(const StringName &p_signal) const { if (_signals.has(p_signal)) { return true; @@ -1188,18 +1291,76 @@ String GDScript::_get_gdscript_reference_class_name(const GDScript *p_gdscript) return class_name; } +GDScript *GDScript::_get_gdscript_from_variant(const Variant &p_variant) { + Object *obj = p_variant; + if (obj == nullptr || obj->get_instance_id().is_null()) { + return nullptr; + } + return Object::cast_to<GDScript>(obj); +} + +void GDScript::_get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except) { + if (p_dependencies.has(this)) { + return; + } + p_dependencies.insert(this); + + for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { + if (E.value == nullptr) { + continue; + } + for (const Variant &V : E.value->constants) { + GDScript *scr = _get_gdscript_from_variant(V); + if (scr != nullptr && scr != p_except) { + scr->_get_dependencies(p_dependencies, p_except); + } + } + } + + if (implicit_initializer) { + for (const Variant &V : implicit_initializer->constants) { + GDScript *scr = _get_gdscript_from_variant(V); + if (scr != nullptr && scr != p_except) { + scr->_get_dependencies(p_dependencies, p_except); + } + } + } + + if (implicit_ready) { + for (const Variant &V : implicit_ready->constants) { + GDScript *scr = _get_gdscript_from_variant(V); + if (scr != nullptr && scr != p_except) { + scr->_get_dependencies(p_dependencies, p_except); + } + } + } + + for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { + if (E.value != p_except) { + E.value->_get_dependencies(p_dependencies, p_except); + } + } + + for (const KeyValue<StringName, Variant> &E : constants) { + GDScript *scr = _get_gdscript_from_variant(E.value); + if (scr != nullptr && scr != p_except) { + scr->_get_dependencies(p_dependencies, p_except); + } + } +} + GDScript::GDScript() : script_list(this) { -#ifdef DEBUG_ENABLED { MutexLock lock(GDScriptLanguage::get_singleton()->mutex); GDScriptLanguage::get_singleton()->script_list.add(&script_list); } -#endif + + path = vformat("gdscript://%d.gd", get_instance_id()); } -void GDScript::_save_orphaned_subclasses() { +void GDScript::_save_orphaned_subclasses(GDScript::ClearData *p_clear_data) { struct ClassRefWithName { ObjectID id; String fully_qualified_name; @@ -1215,8 +1376,17 @@ void GDScript::_save_orphaned_subclasses() { } // clear subclasses to allow unused subclasses to be deleted + for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { + p_clear_data->scripts.insert(E.value); + } subclasses.clear(); // subclasses are also held by constants, clear those as well + for (KeyValue<StringName, Variant> &E : constants) { + GDScript *gdscr = _get_gdscript_from_variant(E.value); + if (gdscr != nullptr) { + p_clear_data->scripts.insert(gdscr); + } + } constants.clear(); // keep orphan subclass only for subclasses that are still in use @@ -1238,58 +1408,59 @@ void GDScript::_init_rpc_methods_properties() { rpc_config = base->rpc_config.duplicate(); } - GDScript *cscript = this; - HashMap<StringName, Ref<GDScript>>::Iterator sub_E = subclasses.begin(); - while (cscript) { - // RPC Methods - for (KeyValue<StringName, GDScriptFunction *> &E : cscript->member_functions) { - Variant config = E.value->get_rpc_config(); - if (config.get_type() != Variant::NIL) { - rpc_config[E.value->get_name()] = config; - } - } - - if (cscript != this) { - ++sub_E; - } - - if (sub_E) { - cscript = sub_E->value.ptr(); - } else { - cscript = nullptr; + // RPC Methods + for (KeyValue<StringName, GDScriptFunction *> &E : member_functions) { + Variant config = E.value->get_rpc_config(); + if (config.get_type() != Variant::NIL) { + rpc_config[E.value->get_name()] = config; } } } -GDScript::~GDScript() { - { - MutexLock lock(GDScriptLanguage::get_singleton()->mutex); +void GDScript::clear(GDScript::ClearData *p_clear_data) { + if (clearing) { + return; + } + clearing = true; - while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) { - // Order matters since clearing the stack may already cause - // the GDSCriptFunctionState to be destroyed and thus removed from the list. - pending_func_states.remove(E); - E->self()->_clear_stack(); - } + GDScript::ClearData data; + GDScript::ClearData *clear_data = p_clear_data; + bool is_root = false; + + // If `clear_data` is `nullptr`, it means that it's the root. + // The root is in charge to clear functions and scripts of itself and its dependencies + if (clear_data == nullptr) { + clear_data = &data; + is_root = true; + } + + RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies(); + for (GDScript *E : must_clear_dependencies) { + clear_data->scripts.insert(E); + E->clear(clear_data); } for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { - memdelete(E.value); + clear_data->functions.insert(E.value); } + member_functions.clear(); - if (implicit_initializer) { - memdelete(implicit_initializer); + for (KeyValue<StringName, GDScript::MemberInfo> &E : member_indices) { + clear_data->scripts.insert(E.value.data_type.script_type_ref); + E.value.data_type.script_type_ref = Ref<Script>(); } - if (implicit_ready) { - memdelete(implicit_ready); + if (implicit_initializer) { + clear_data->functions.insert(implicit_initializer); + implicit_initializer = nullptr; } - if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown. - GDScriptCache::remove_script(get_path()); + if (implicit_ready) { + clear_data->functions.insert(implicit_ready); + implicit_ready = nullptr; } - _save_orphaned_subclasses(); + _save_orphaned_subclasses(clear_data); #ifdef TOOLS_ENABLED // Clearing inner class doc, script doc only cleared when the script source deleted. @@ -1298,13 +1469,50 @@ GDScript::~GDScript() { } #endif -#ifdef DEBUG_ENABLED + // If it's not the root, skip clearing the data + if (is_root) { + // All dependencies have been accounted for + for (GDScriptFunction *E : clear_data->functions) { + memdelete(E); + } + for (Ref<Script> &E : clear_data->scripts) { + Ref<GDScript> gdscr = E; + if (gdscr.is_valid()) { + GDScriptCache::remove_script(gdscr->get_path()); + } + } + clear_data->clear(); + } +} + +GDScript::~GDScript() { + if (destructing) { + return; + } + destructing = true; + + clear(); + + { + MutexLock lock(GDScriptLanguage::get_singleton()->mutex); + + while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) { + // Order matters since clearing the stack may already cause + // the GDScriptFunctionState to be destroyed and thus removed from the list. + pending_func_states.remove(E); + E->self()->_clear_stack(); + } + } + { MutexLock lock(GDScriptLanguage::get_singleton()->mutex); GDScriptLanguage::get_singleton()->script_list.remove(&script_list); } -#endif + + if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown. + GDScriptCache::remove_script(get_path()); + } } ////////////////////////////// @@ -1770,6 +1978,16 @@ void GDScriptLanguage::add_named_global_constant(const StringName &p_name, const named_globals[p_name] = p_value; } +Variant GDScriptLanguage::get_any_global_constant(const StringName &p_name) { + if (named_globals.has(p_name)) { + return named_globals[p_name]; + } + if (globals.has(p_name)) { + return _global_array[globals[p_name]]; + } + ERR_FAIL_V_MSG(Variant(), vformat("Could not find any global constant with name: %s.", p_name)); +} + void GDScriptLanguage::remove_named_global_constant(const StringName &p_name) { ERR_FAIL_COND(!named_globals.has(p_name)); named_globals.erase(p_name); @@ -1826,6 +2044,42 @@ Error GDScriptLanguage::execute_file(const String &p_path) { } void GDScriptLanguage::finish() { + if (_call_stack) { + memdelete_arr(_call_stack); + _call_stack = nullptr; + } + + // Clear the cache before parsing the script_list + GDScriptCache::clear(); + + // Clear dependencies between scripts, to ensure cyclic references are broken + // (to avoid leaks at exit). + SelfList<GDScript> *s = script_list.first(); + while (s) { + // This ensures the current script is not released before we can check + // what's the next one in the list (we can't get the next upfront because we + // don't know if the reference breaking will cause it -or any other after + // it, for that matter- to be released so the next one is not the same as + // before). + Ref<GDScript> scr = s->self(); + if (scr.is_valid()) { + for (KeyValue<StringName, GDScriptFunction *> &E : scr->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 (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) { + E.value.data_type.script_type_ref = Ref<Script>(); + } + + // Clear backup for scripts that could slip out of the cyclic reference + // check + scr->clear(); + } + s = s->next(); + } } void GDScriptLanguage::profiling_start() { @@ -1935,7 +2189,8 @@ void GDScriptLanguage::reload_all_scripts() { SelfList<GDScript> *elem = script_list.first(); while (elem) { - if (elem->self()->get_path().is_resource_file()) { + // Scripts will reload all subclasses, so only reload root scripts. + if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) { print_verbose("GDScript: Found: " + elem->self()->get_path()); scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident } @@ -1964,7 +2219,8 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so SelfList<GDScript> *elem = script_list.first(); while (elem) { - if (elem->self()->get_path().is_resource_file()) { + // Scripts will reload all subclasses, so only reload root scripts. + if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) { scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident } elem = elem->next(); @@ -2192,7 +2448,6 @@ bool GDScriptLanguage::handles_global_class_type(const String &p_type) const { } String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const { - Vector<uint8_t> sourcef; Error err; Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err); if (err) { @@ -2203,88 +2458,31 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b GDScriptParser parser; err = parser.parse(source, p_path, false); + if (err) { + return String(); + } - // TODO: Simplify this code by using the analyzer to get full inheritance. - if (err == OK) { - const GDScriptParser::ClassNode *c = parser.get_tree(); - if (r_icon_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_relative_path()) { - *r_icon_path = p_path.get_base_dir().path_join(c->icon_path).simplify_path(); - } - } - if (r_base_type) { - const GDScriptParser::ClassNode *subclass = c; - String path = p_path; - GDScriptParser subparser; - while (subclass) { - if (subclass->extends_used) { - if (!subclass->extends_path.is_empty()) { - if (subclass->extends.size() == 0) { - get_global_class_name(subclass->extends_path, r_base_type); - subclass = nullptr; - break; - } else { - Vector<StringName> extend_classes = subclass->extends; - - Ref<FileAccess> subfile = FileAccess::open(subclass->extends_path, FileAccess::READ); - if (subfile.is_null()) { - break; - } - String subsource = subfile->get_as_utf8_string(); - - if (subsource.is_empty()) { - break; - } - String subpath = subclass->extends_path; - if (subpath.is_relative_path()) { - subpath = path.get_base_dir().path_join(subpath).simplify_path(); - } - - if (OK != subparser.parse(subsource, subpath, false)) { - break; - } - path = subpath; - subclass = subparser.get_tree(); - - while (extend_classes.size() > 0) { - bool found = false; - for (int i = 0; i < subclass->members.size(); i++) { - if (subclass->members[i].type != GDScriptParser::ClassNode::Member::CLASS) { - continue; - } - - const GDScriptParser::ClassNode *inner_class = subclass->members[i].m_class; - if (inner_class->identifier->name == extend_classes[0]) { - extend_classes.remove_at(0); - found = true; - subclass = inner_class; - break; - } - } - if (!found) { - subclass = nullptr; - break; - } - } - } - } else if (subclass->extends.size() == 1) { - *r_base_type = subclass->extends[0]; - subclass = nullptr; - } else { - break; - } - } else { - *r_base_type = "RefCounted"; - subclass = nullptr; - } - } + GDScriptAnalyzer analyzer(&parser); + err = analyzer.resolve_inheritance(); + if (err) { + return String(); + } + + const GDScriptParser::ClassNode *c = parser.get_tree(); + + if (r_base_type) { + *r_base_type = c->get_datatype().native_type; + } + + if (r_icon_path) { + if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) { + *r_icon_path = c->icon_path.simplify_path(); + } else if (c->icon_path.is_relative_path()) { + *r_icon_path = p_path.get_base_dir().path_join(c->icon_path).simplify_path(); } - return c->identifier != nullptr ? String(c->identifier->name) : String(); } - return String(); + return c->identifier != nullptr ? String(c->identifier->name) : String(); } GDScriptLanguage::GDScriptLanguage() { @@ -2306,8 +2504,7 @@ GDScriptLanguage::GDScriptLanguage() { script_frame_time = 0; _debug_call_stack_pos = 0; - int dmcs = GLOBAL_DEF("debug/settings/gdscript/max_call_stack", 1024); - ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/gdscript/max_call_stack", PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "1024,4096,1,or_greater")); //minimum is 1024 + int dmcs = GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "1024,4096,1,or_greater"), 1024); if (EngineDebugger::is_active()) { //debugging enabled! @@ -2328,44 +2525,12 @@ GDScriptLanguage::GDScriptLanguage() { GDScriptWarning::Code code = (GDScriptWarning::Code)i; Variant default_enabled = GDScriptWarning::get_default_value(code); String path = GDScriptWarning::get_settings_path_from_code(code); - GLOBAL_DEF(path, default_enabled); - - PropertyInfo property_info = GDScriptWarning::get_property_info(code); - ProjectSettings::get_singleton()->set_custom_property_info(path, property_info); + GLOBAL_DEF(GDScriptWarning::get_property_info(code), default_enabled); } #endif // DEBUG_ENABLED } GDScriptLanguage::~GDScriptLanguage() { - if (_call_stack) { - memdelete_arr(_call_stack); - } - - // Clear dependencies between scripts, to ensure cyclic references are broken (to avoid leaks at exit). - SelfList<GDScript> *s = script_list.first(); - while (s) { - GDScript *scr = s->self(); - // This ensures the current script is not released before we can check what's the next one - // in the list (we can't get the next upfront because we don't know if the reference breaking - // will cause it -or any other after it, for that matter- to be released so the next one - // is not the same as before). - scr->reference(); - - for (KeyValue<StringName, GDScriptFunction *> &E : scr->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 (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) { - E.value.data_type.script_type_ref = Ref<Script>(); - } - - s = s->next(); - scr->unreference(); - } - singleton = nullptr; } @@ -2387,6 +2552,26 @@ Ref<GDScript> GDScriptLanguage::get_orphan_subclass(const String &p_qualified_na return Ref<GDScript>(Object::cast_to<GDScript>(obj)); } +Ref<GDScript> GDScriptLanguage::get_script_by_fully_qualified_name(const String &p_name) { + { + MutexLock lock(mutex); + + SelfList<GDScript> *elem = script_list.first(); + while (elem) { + GDScript *scr = elem->self(); + if (scr->fully_qualified_name == p_name) { + return scr; + } + elem = elem->next(); + } + } + + Ref<GDScript> scr; + scr.instantiate(); + scr->fully_qualified_name = p_name; + return scr; +} + /*************** RESOURCE ***************/ Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { @@ -2397,8 +2582,6 @@ Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const Str Error err; Ref<GDScript> scr = GDScriptCache::get_full_script(p_path, err, "", p_cache_mode == CACHE_MODE_IGNORE); - // TODO: Reintroduce binary and encrypted scripts. - if (scr.is_null()) { // Don't fail loading because of parsing error. scr.instantiate(); @@ -2413,9 +2596,6 @@ Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const Str void ResourceFormatLoaderGDScript::get_recognized_extensions(List<String> *p_extensions) const { p_extensions->push_back("gd"); - // TODO: Reintroduce binary and encrypted scripts. - // p_extensions->push_back("gdc"); - // p_extensions->push_back("gde"); } bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const { @@ -2424,8 +2604,7 @@ bool ResourceFormatLoaderGDScript::handles_type(const String &p_type) const { String ResourceFormatLoaderGDScript::get_resource_type(const String &p_path) const { String el = p_path.get_extension().to_lower(); - // TODO: Reintroduce binary and encrypted scripts. - if (el == "gd" /*|| el == "gdc" || el == "gde"*/) { + if (el == "gd") { return "GDScript"; } return ""; diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 72ad890fbc..82d04f641c 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_H #define GDSCRIPT_H @@ -61,6 +61,7 @@ class GDScript : public Script { GDCLASS(GDScript, Script); bool tool = false; bool valid = false; + bool reloading = false; struct MemberInfo { int index = 0; @@ -69,6 +70,15 @@ class GDScript : public Script { GDScriptDataType data_type; }; + struct ClearData { + RBSet<GDScriptFunction *> functions; + RBSet<Ref<Script>> scripts; + void clear() { + functions.clear(); + scripts.clear(); + } + }; + friend class GDScriptInstance; friend class GDScriptFunction; friend class GDScriptAnalyzer; @@ -124,6 +134,8 @@ class GDScript : public Script { int subclass_count = 0; RBSet<Object *> instances; + bool destructing = false; + bool clearing = false; //exported members String source; String path; @@ -137,7 +149,6 @@ class GDScript : public 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_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 @@ -154,7 +165,7 @@ class GDScript : public Script { bool _update_exports(bool *r_err = nullptr, bool p_recursive_call = false, PlaceHolderScriptInstance *p_instance_to_update = nullptr); - void _save_orphaned_subclasses(); + void _save_orphaned_subclasses(GDScript::ClearData *p_clear_data); void _init_rpc_methods_properties(); void _get_script_property_list(List<PropertyInfo> *r_list, bool p_include_base) const; @@ -164,6 +175,9 @@ class GDScript : public Script { // This method will map the class name from "RefCounted" to "MyClass.InnerClass". static String _get_gdscript_reference_class_name(const GDScript *p_gdscript); + GDScript *_get_gdscript_from_variant(const Variant &p_variant); + void _get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except); + protected: bool _get(const StringName &p_name, Variant &r_ret) const; bool _set(const StringName &p_name, const Variant &p_value); @@ -174,10 +188,17 @@ protected: static void _bind_methods(); public: + void clear(GDScript::ClearData *p_clear_data = nullptr); + virtual bool is_valid() const override { return valid; } bool inherits_script(const Ref<Script> &p_script) const override; + GDScript *find_class(const String &p_qualified_name); + bool has_class(const GDScript *p_script); + GDScript *get_root_script(); + bool is_root_script() const { return _owner == nullptr; } + String get_fully_qualified_name() const { return fully_qualified_name; } const HashMap<StringName, Ref<GDScript>> &get_subclasses() const { return subclasses; } const HashMap<StringName, Variant> &get_constants() const { return constants; } const HashSet<StringName> &get_members() const { return members; } @@ -189,6 +210,10 @@ public: const Ref<GDScriptNativeClass> &get_native() const { return native; } const String &get_script_class_name() const { return name; } + RBSet<GDScript *> get_dependencies(); + RBSet<GDScript *> get_inverted_dependencies(); + RBSet<GDScript *> get_must_clear_dependencies(); + virtual bool has_script_signal(const StringName &p_signal) const override; virtual void get_script_signal_list(List<MethodInfo> *r_signals) const override; @@ -203,6 +228,7 @@ public: virtual bool can_instantiate() const override; virtual Ref<Script> get_base_script() const override; + virtual StringName get_global_name() const override; virtual StringName get_instance_base_type() const override; // this may not work in all scripts, will return empty if so virtual ScriptInstance *instance_create(Object *p_this) override; @@ -223,11 +249,8 @@ public: virtual Error reload(bool p_keep_state = false) override; virtual void set_path(const String &p_path, bool p_take_over = false) override; - void set_script_path(const String &p_path) { path = p_path; } //because subclasses need a path too... + String get_script_path() const; Error load_source_code(const String &p_path); - Error load_byte_code(const String &p_path); - - Vector<uint8_t> get_as_byte_code() const; bool get_property_default_value(const StringName &p_property, Variant &r_value) const override; @@ -267,6 +290,7 @@ class GDScriptInstance : public ScriptInstance { friend class GDScriptLambdaCallable; friend class GDScriptLambdaSelfCallable; friend class GDScriptCompiler; + friend class GDScriptCache; friend struct GDScriptUtilityFunctionsDefinitions; ObjectID owner_id; @@ -415,7 +439,7 @@ public: csi.write[_debug_call_stack_pos - i - 1].line = _call_stack[i].line ? *_call_stack[i].line : 0; if (_call_stack[i].function) { csi.write[_debug_call_stack_pos - i - 1].func = _call_stack[i].function->get_name(); - csi.write[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_path(); + csi.write[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_script_path(); } } return csi; @@ -437,6 +461,9 @@ public: _FORCE_INLINE_ Variant *get_global_array() { return _global_array; } _FORCE_INLINE_ const HashMap<StringName, int> &get_global_map() const { return globals; } _FORCE_INLINE_ const HashMap<StringName, Variant> &get_named_globals_map() const { return named_globals; } + // These two functions should be used when behavior needs to be consistent between in-editor and running the scene + bool has_any_global_constant(const StringName &p_name) { return named_globals.has(p_name) || globals.has(p_name); } + Variant get_any_global_constant(const StringName &p_name); _FORCE_INLINE_ static GDScriptLanguage *get_singleton() { return singleton; } @@ -515,6 +542,8 @@ public: void add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass); Ref<GDScript> get_orphan_subclass(const String &p_qualified_name); + Ref<GDScript> get_script_by_fully_qualified_name(const String &p_name); + GDScriptLanguage(); ~GDScriptLanguage(); }; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 898e4eb1a6..d7f6126207 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -1,37 +1,38 @@ -/*************************************************************************/ -/* gdscript_analyzer.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_analyzer.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_analyzer.h" #include "core/config/engine.h" #include "core/config/project_settings.h" +#include "core/core_string_names.h" #include "core/io/file_access.h" #include "core/io/resource_loader.h" #include "core/object/class_db.h" @@ -39,6 +40,10 @@ #include "core/templates/hash_map.h" #include "gdscript.h" #include "gdscript_utility_functions.h" +#include "scene/resources/packed_scene.h" + +#define UNNAMED_ENUM "<anonymous enum>" +#define ENUM_SEPARATOR "::" static MethodInfo info_from_utility_func(const StringName &p_function) { ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo()); @@ -63,9 +68,6 @@ static MethodInfo info_from_utility_func(const StringName &p_function) { pi.name = "arg" + itos(i + 1); #endif pi.type = Variant::get_utility_function_argument_type(p_function, i); - if (pi.type == Variant::NIL) { - pi.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - } info.arguments.push_back(pi); } } @@ -98,19 +100,45 @@ static GDScriptParser::DataType make_native_meta_type(const StringName &p_class_ type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; type.kind = GDScriptParser::DataType::NATIVE; type.builtin_type = Variant::OBJECT; - type.is_constant = true; type.native_type = p_class_name; + type.is_constant = true; type.is_meta_type = true; return type; } -static GDScriptParser::DataType make_native_enum_type(const StringName &p_native_class, const StringName &p_enum_name) { +static GDScriptParser::DataType make_script_meta_type(const Ref<Script> &p_script) { GDScriptParser::DataType type; type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - type.kind = GDScriptParser::DataType::ENUM; - type.builtin_type = Variant::INT; + type.kind = GDScriptParser::DataType::SCRIPT; + type.builtin_type = Variant::OBJECT; + type.native_type = p_script->get_instance_base_type(); + type.script_type = p_script; + type.script_path = p_script->get_path(); type.is_constant = true; type.is_meta_type = true; + return type; +} + +// In enum types, native_type is used to store the class (native or otherwise) that the enum belongs to. +// This disambiguates between similarly named enums in base classes or outer classes +static GDScriptParser::DataType make_enum_type(const StringName &p_enum_name, const String &p_base_name, const bool p_meta = false) { + GDScriptParser::DataType type; + type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + type.kind = GDScriptParser::DataType::ENUM; + type.builtin_type = p_meta ? Variant::DICTIONARY : Variant::INT; + type.enum_type = p_enum_name; + type.is_constant = true; + type.is_meta_type = p_meta; + + // For enums, native_type is only used to check compatibility in is_type_compatible() + // We can set anything readable here for error messages, as long as it uniquely identifies the type of the enum + type.native_type = p_base_name + ENUM_SEPARATOR + p_enum_name; + + return type; +} + +static GDScriptParser::DataType make_native_enum_type(const StringName &p_enum_name, const StringName &p_native_class, const bool p_meta = true) { + GDScriptParser::DataType type = make_enum_type(p_enum_name, p_native_class, p_meta); List<StringName> enum_values; ClassDB::get_enum_constants(p_native_class, p_enum_name, &enum_values); @@ -132,7 +160,20 @@ 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) { +static StringName enum_get_value_name(const GDScriptParser::DataType p_type, int64_t p_val) { + // Check that an enum has a given value, not key. + // Make sure that implicit conversion to int64_t is sensible before calling! + HashMap<StringName, int64_t>::ConstIterator i = p_type.enum_values.begin(); + while (i) { + if (i->value == p_val) { + return i->key; + } + ++i; + } + return StringName(); +} + +bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_member) { 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]; @@ -145,6 +186,9 @@ bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName member->type == GDScriptParser::ClassNode::Member::SIGNAL) { return true; } + if (p_member->type != GDScriptParser::Node::FUNCTION && member->type == GDScriptParser::ClassNode::Member::FUNCTION) { + return true; + } } return false; @@ -160,6 +204,9 @@ bool GDScriptAnalyzer::has_member_name_conflict_in_native_type(const StringName if (ClassDB::has_integer_constant(p_native_type_string, p_member_name)) { return true; } + if (p_member_name == CoreStringNames::get_singleton()->_script) { + return true; + } return false; } @@ -184,17 +231,22 @@ Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_me } Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node) { + // TODO check outer classes for static members only 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); + if (has_member_name_conflict_in_script_class(p_member_name, current_class_node, p_member_node)) { + String parent_class_name = current_class_node->fqcn; + if (current_class_node->identifier != nullptr) { + parent_class_name = current_class_node->identifier->name; + } + push_error(vformat(R"(The member "%s" already exists in parent class %s.)", p_member_name, parent_class_name), p_member_node); return ERR_PARSE_ERROR; } current_data_type = ¤t_class_node->base_type; } + // No need for native class recursion because Node exposes all Object's properties. 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( @@ -207,25 +259,80 @@ Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::C return OK; } -Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) { - if (p_class->base_type.is_set()) { - // Already resolved +void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list) { + ERR_FAIL_NULL(p_node); + ERR_FAIL_NULL(p_list); + + if (p_list->find(p_node) != nullptr) { + return; + } + + p_list->push_back(p_node); + + // TODO: Try to solve class inheritance if not yet resolving. + + // Prioritize node base type over its outer class + if (p_node->base_type.class_type != nullptr) { + get_class_node_current_scope_classes(p_node->base_type.class_type, p_list); + } + + if (p_node->outer != nullptr) { + get_class_node_current_scope_classes(p_node->outer, p_list); + } +} + +Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source) { + if (p_source == nullptr && parser->has_class(p_class)) { + p_source = p_class; + } + + if (p_class->base_type.is_resolving()) { + push_error(vformat(R"(Could not resolve class "%s": Cyclic reference.)", type_from_metatype(p_class->get_datatype()).to_string()), p_source); + return ERR_PARSE_ERROR; + } + + if (!p_class->base_type.has_no_type()) { + // Already resolved. return OK; } - if (p_class == parser->head) { - if (p_class->identifier) { - p_class->fqcn = p_class->identifier->name; - } else { - p_class->fqcn = parser->script_path; + if (!parser->has_class(p_class)) { + String script_path = p_class->get_datatype().script_path; + Ref<GDScriptParserRef> parser_ref = get_parser_for(script_path); + if (parser_ref.is_null()) { + push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); + return ERR_PARSE_ERROR; } - } else { - p_class->fqcn = p_class->outer->fqcn + "::" + String(p_class->identifier->name); + + Error err = parser_ref->raise_status(GDScriptParserRef::PARSED); + if (err) { + push_error(vformat(R"(Could not parse script "%s": %s.)", script_path, error_names[err]), p_source); + return ERR_PARSE_ERROR; + } + + ERR_FAIL_COND_V_MSG(!parser_ref->get_parser()->has_class(p_class), ERR_PARSE_ERROR, R"(Parser bug: Mismatched external parser.)"); + + GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer(); + GDScriptParser *other_parser = parser_ref->get_parser(); + + int error_count = other_parser->errors.size(); + other_analyzer->resolve_class_inheritance(p_class); + if (other_parser->errors.size() > error_count) { + push_error(vformat(R"(Could not resolve inheritance for class "%s".)", p_class->fqcn), p_source); + return ERR_PARSE_ERROR; + } + + return OK; } + GDScriptParser::ClassNode *previous_class = parser->current_class; + parser->current_class = p_class; + if (p_class->identifier) { StringName class_name = p_class->identifier->name; - if (class_exists(class_name)) { + if (GDScriptParser::get_builtin_type(class_name) < Variant::VARIANT_MAX) { + push_error(vformat(R"(Class "%s" hides a built-in type.)", class_name), p_class->identifier); + } else 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); @@ -234,7 +341,9 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, } } - GDScriptParser::DataType result; + GDScriptParser::DataType resolving_datatype; + resolving_datatype.kind = GDScriptParser::DataType::RESOLVING; + p_class->base_type = resolving_datatype; // Set datatype for class. GDScriptParser::DataType class_type; @@ -247,6 +356,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, class_type.builtin_type = Variant::OBJECT; p_class->set_datatype(class_type); + GDScriptParser::DataType result; if (!p_class->extends_used) { result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; result.kind = GDScriptParser::DataType::NATIVE; @@ -268,7 +378,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return ERR_PARSE_ERROR; } - Error err = ext_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + Error err = ext_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", p_class->extends_path), p_class); return err; @@ -295,7 +405,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return ERR_PARSE_ERROR; } - Error err = base_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + Error err = base_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); return err; @@ -304,7 +414,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, } } else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) { const ProjectSettings::AutoloadInfo &info = ProjectSettings::get_singleton()->get_autoload(name); - if (info.path.get_extension().to_lower() != ".gd") { + if (info.path.get_extension().to_lower() != GDScriptLanguage::get_singleton()->get_extension()) { push_error(vformat(R"(Singleton %s is not a GDScript.)", info.name), p_class); return ERR_PARSE_ERROR; } @@ -315,22 +425,24 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, return ERR_PARSE_ERROR; } - Error err = info_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + Error err = info_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); if (err != OK) { push_error(vformat(R"(Could not resolve super class inheritance from "%s".)", name), p_class); return err; } - } else if (class_exists(name) && ClassDB::can_instantiate(name)) { + base = info_parser->get_parser()->head->get_datatype(); + } else if (class_exists(name)) { base.kind = GDScriptParser::DataType::NATIVE; base.native_type = name; } else { // Look for other classes in script. - GDScriptParser::ClassNode *look_class = p_class; bool found = false; - while (look_class != nullptr) { + List<GDScriptParser::ClassNode *> script_classes; + get_class_node_current_scope_classes(p_class, &script_classes); + for (GDScriptParser::ClassNode *look_class : script_classes) { if (look_class->identifier && look_class->identifier->name == name) { if (!look_class->get_datatype().is_set()) { - Error err = resolve_inheritance(look_class, false); + Error err = resolve_class_inheritance(look_class, p_class); if (err) { return err; } @@ -339,19 +451,12 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, found = true; break; } - if (look_class->members_indices.has(name) && look_class->get_member(name).type == GDScriptParser::ClassNode::Member::CLASS) { - GDScriptParser::ClassNode::Member member = look_class->get_member(name); - if (!member.m_class->get_datatype().is_set()) { - Error err = resolve_inheritance(member.m_class, false); - if (err) { - return err; - } - } - base = member.m_class->get_datatype(); + if (look_class->has_member(name)) { + resolve_class_member(look_class, name, p_class); + base = look_class->get_member(name).get_datatype(); found = true; break; } - look_class = look_class->outer; } if (!found) { @@ -384,7 +489,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, result = base; } - if (!result.is_set()) { + if (!result.is_set() || result.has_no_type()) { // TODO: More specific error messages. push_error(vformat(R"(Could not resolve inheritance for class "%s".)", p_class->identifier == nullptr ? "<main>" : p_class->identifier->name), p_class); return ERR_PARSE_ERROR; @@ -404,10 +509,21 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, class_type.native_type = result.native_type; p_class->set_datatype(class_type); + parser->current_class = previous_class; + + return OK; +} + +Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) { + Error err = resolve_class_inheritance(p_class); + if (err) { + return err; + } + if (p_recursive) { for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type == GDScriptParser::ClassNode::Member::CLASS) { - Error err = resolve_inheritance(p_class->members[i].m_class, true); + err = resolve_class_inheritance(p_class->members[i].m_class, true); if (err) { return err; } @@ -419,14 +535,29 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, } GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::TypeNode *p_type) { - GDScriptParser::DataType result; + GDScriptParser::DataType bad_type; + bad_type.kind = GDScriptParser::DataType::VARIANT; + bad_type.type_source = GDScriptParser::DataType::INFERRED; if (p_type == nullptr) { - result.kind = GDScriptParser::DataType::VARIANT; - return result; + return bad_type; + } + + if (p_type->get_datatype().is_resolving()) { + push_error(R"(Could not resolve datatype: Cyclic reference.)", p_type); + return bad_type; } - result.type_source = result.ANNOTATED_EXPLICIT; + if (!p_type->get_datatype().has_no_type()) { + return p_type->get_datatype(); + } + + GDScriptParser::DataType resolving_datatype; + resolving_datatype.kind = GDScriptParser::DataType::RESOLVING; + p_type->set_datatype(resolving_datatype); + + GDScriptParser::DataType result; + result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; result.builtin_type = Variant::OBJECT; if (p_type->type_chain.is_empty()) { @@ -440,39 +571,28 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type StringName first = p_type->type_chain[0]->name; if (first == SNAME("Variant")) { - result.kind = GDScriptParser::DataType::VARIANT; if (p_type->type_chain.size() > 1) { - push_error(R"("Variant" type don't contain nested types.)", p_type->type_chain[1]); - return GDScriptParser::DataType(); + // TODO: Variant does actually have a nested Type though. + push_error(R"(Variant doesn't contain nested types.)", p_type->type_chain[1]); + return bad_type; } - return result; - } - - if (first == SNAME("Object")) { + result.kind = GDScriptParser::DataType::VARIANT; + } else if (first == SNAME("Object")) { + // Object is treated like a native type, not a built-in. result.kind = GDScriptParser::DataType::NATIVE; result.native_type = SNAME("Object"); - if (p_type->type_chain.size() > 1) { - push_error(R"("Object" type don't contain nested types.)", p_type->type_chain[1]); - return GDScriptParser::DataType(); - } - return result; - } - - if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) { + } else if (GDScriptParser::get_builtin_type(first) < Variant::VARIANT_MAX) { // Built-in types. if (p_type->type_chain.size() > 1) { push_error(R"(Built-in types don't contain nested types.)", p_type->type_chain[1]); - return GDScriptParser::DataType(); + return bad_type; } result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = GDScriptParser::get_builtin_type(first); if (result.builtin_type == Variant::ARRAY) { - GDScriptParser::DataType container_type = resolve_datatype(p_type->container_type); - + GDScriptParser::DataType container_type = type_from_metatype(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); } } @@ -488,92 +608,84 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type String ext = path.get_extension(); if (ext == GDScriptLanguage::get_singleton()->get_extension()) { Ref<GDScriptParserRef> ref = get_parser_for(path); - if (!ref.is_valid() || ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) { + if (!ref.is_valid() || ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) { push_error(vformat(R"(Could not parse global class "%s" from "%s".)", first, ScriptServer::get_global_class_path(first)), p_type); - return GDScriptParser::DataType(); + return bad_type; } result = ref->get_parser()->head->get_datatype(); } else { - result.kind = GDScriptParser::DataType::SCRIPT; - result.native_type = ScriptServer::get_global_class_native_base(first); - result.script_type = ResourceLoader::load(path, "Script"); - result.script_path = path; - result.is_constant = true; - result.is_meta_type = false; + result = make_script_meta_type(ResourceLoader::load(path, "Script")); } } } else if (ProjectSettings::get_singleton()->has_autoload(first) && ProjectSettings::get_singleton()->get_autoload(first).is_singleton) { const ProjectSettings::AutoloadInfo &autoload = ProjectSettings::get_singleton()->get_autoload(first); Ref<GDScriptParserRef> ref = get_parser_for(autoload.path); - if (ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) { + if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) { push_error(vformat(R"(Could not parse singleton "%s" from "%s".)", first, autoload.path), p_type); - return GDScriptParser::DataType(); + return bad_type; } result = ref->get_parser()->head->get_datatype(); } 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); + result = make_native_enum_type(first, parser->current_class->base_type.native_type); } else { // Classes in current scope. - GDScriptParser::ClassNode *script_class = parser->current_class; + List<GDScriptParser::ClassNode *> script_classes; bool found = false; - while (!found && script_class != nullptr) { + get_class_node_current_scope_classes(parser->current_class, &script_classes); + for (GDScriptParser::ClassNode *script_class : script_classes) { + if (found) { + break; + } + if (script_class->identifier && script_class->identifier->name == first) { result = script_class->get_datatype(); - found = true; break; } if (script_class->members_indices.has(first)) { - GDScriptParser::ClassNode::Member member = script_class->members[script_class->members_indices[first]]; + resolve_class_member(script_class, first, p_type); + + GDScriptParser::ClassNode::Member member = script_class->get_member(first); switch (member.type) { case GDScriptParser::ClassNode::Member::CLASS: - result = member.m_class->get_datatype(); + result = member.get_datatype(); found = true; break; case GDScriptParser::ClassNode::Member::ENUM: - result = member.m_enum->get_datatype(); + result = member.get_datatype(); found = true; break; case GDScriptParser::ClassNode::Member::CONSTANT: - if (member.constant->get_datatype().is_meta_type) { - result = member.constant->get_datatype(); - result.is_meta_type = false; + if (member.get_datatype().is_meta_type) { + result = member.get_datatype(); 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(); + Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_script_path()); + if (ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED) != OK) { + push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), p_type); + return bad_type; } result = ref->get_parser()->head->get_datatype(); - result.is_meta_type = false; } else { - Ref<Script> 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(); - result.is_meta_type = false; + result = make_script_meta_type(member.constant->initializer->reduced_value); } + found = true; break; } [[fallthrough]]; default: push_error(vformat(R"("%s" is a %s but does not contain a type.)", first, member.get_type_name()), p_type); - return GDScriptParser::DataType(); + return bad_type; } } - script_class = script_class->outer; } } if (!result.is_set()) { push_error(vformat(R"("%s" was not found in the current scope.)", first), p_type); - result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type. - return result; + return bad_type; } if (p_type->type_chain.size() > 1) { @@ -584,12 +696,10 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type result = p_type->type_chain[i]->get_datatype(); if (!result.is_set()) { push_error(vformat(R"(Could not find type "%s" under base "%s".)", p_type->type_chain[i]->name, base.to_string()), p_type->type_chain[1]); - result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type. - return result; + return bad_type; } else if (!result.is_meta_type) { push_error(vformat(R"(Member "%s" under base "%s" is not a valid type.)", p_type->type_chain[i]->name, base.to_string()), p_type->type_chain[1]); - result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type. - return result; + return bad_type; } } } else if (result.kind == GDScriptParser::DataType::NATIVE) { @@ -597,14 +707,17 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type 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]); + return bad_type; } else { - result = make_native_enum_type(result.native_type, p_type->type_chain[1]->name); + result = make_native_enum_type(p_type->type_chain[1]->name, result.native_type); } + } else { + push_error(vformat(R"(Could not find type "%s" in "%s".)", p_type->type_chain[1]->name, first), p_type->type_chain[1]); + return bad_type; } } else { push_error(vformat(R"(Could not find nested type "%s" under base "%s".)", p_type->type_chain[1]->name, result.to_string()), p_type->type_chain[1]); - result.kind = GDScriptParser::DataType::VARIANT; // Leave Variant anyway so future type check don't use an unresolved type. - return result; + return bad_type; } } @@ -616,192 +729,128 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return result; } -void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_class) { - if (p_class->resolved_interface) { +void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, StringName p_name, const GDScriptParser::Node *p_source) { + ERR_FAIL_COND(!p_class->has_member(p_name)); + resolve_class_member(p_class, p_class->members_indices[p_name], p_source); +} + +void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, int p_index, const GDScriptParser::Node *p_source) { + ERR_FAIL_INDEX(p_index, p_class->members.size()); + + GDScriptParser::ClassNode::Member &member = p_class->members.write[p_index]; + if (p_source == nullptr && parser->has_class(p_class)) { + p_source = member.get_source_node(); + } + + if (member.get_datatype().is_resolving()) { + push_error(vformat(R"(Could not resolve member "%s": Cyclic reference.)", member.get_name()), p_source); return; } - p_class->resolved_interface = true; - GDScriptParser::ClassNode *previous_class = parser->current_class; - parser->current_class = p_class; + if (member.get_datatype().is_set()) { + return; + } - for (int i = 0; i < p_class->members.size(); i++) { - GDScriptParser::ClassNode::Member member = p_class->members[i]; + if (!parser->has_class(p_class)) { + String script_path = p_class->get_datatype().script_path; + Ref<GDScriptParserRef> parser_ref = get_parser_for(script_path); + if (parser_ref.is_null()) { + push_error(vformat(R"(Could not find script "%s" (While resolving "%s").)", script_path, member.get_name()), p_source); + return; + } - switch (member.type) { - case GDScriptParser::ClassNode::Member::VARIABLE: { - check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable); + Error err = parser_ref->raise_status(GDScriptParserRef::PARSED); + if (err) { + push_error(vformat(R"(Could not resolve script "%s": %s (While resolving "%s").)", script_path, error_names[err], member.get_name()), p_source); + return; + } - GDScriptParser::DataType datatype; - datatype.kind = GDScriptParser::DataType::VARIANT; - datatype.type_source = GDScriptParser::DataType::UNDETECTED; + ERR_FAIL_COND_MSG(!parser_ref->get_parser()->has_class(p_class), R"(Parser bug: Mismatched external parser.)"); - GDScriptParser::DataType specified_type; - if (member.variable->datatype_specifier != nullptr) { - specified_type = resolve_datatype(member.variable->datatype_specifier); - specified_type.is_meta_type = false; - } + GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer(); + GDScriptParser *other_parser = parser_ref->get_parser(); - if (member.variable->initializer != nullptr) { - member.variable->set_datatype(datatype); // Allow recursive usage. - reduce_expression(member.variable->initializer); - if ((member.variable->infer_datatype || (member.variable->datatype_specifier != nullptr && specified_type.has_container_element_type())) && member.variable->initializer->type == GDScriptParser::Node::ARRAY) { - // Typed array. - GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(member.variable->initializer); - // Can only infer typed array if it has elements. - if ((member.variable->infer_datatype && array->elements.size() > 0) || member.variable->datatype_specifier != nullptr) { - update_array_literal_element_type(specified_type, array); - } - } - datatype = member.variable->initializer->get_datatype(); - if (datatype.type_source != GDScriptParser::DataType::UNDETECTED) { - datatype.type_source = GDScriptParser::DataType::INFERRED; - } - } + int error_count = other_parser->errors.size(); + other_analyzer->resolve_class_member(p_class, p_index); + if (other_parser->errors.size() > error_count) { + push_error(vformat(R"(Could not resolve member "%s".)", member.get_name()), p_source); + } - // Check if initializer is an unset identifier (ie: a variable within scope, but declared below) - if (member.variable->initializer && !member.variable->initializer->get_datatype().is_set()) { - if (member.variable->initializer->type == GDScriptParser::Node::IDENTIFIER) { - GDScriptParser::IdentifierNode *initializer_identifier = static_cast<GDScriptParser::IdentifierNode *>(member.variable->initializer); - push_error(vformat(R"(Identifier "%s" must be declared above current variable.)", initializer_identifier->name), member.variable->initializer); - } else { - ERR_PRINT("Parser bug (please report): tried to assign unset node without an identifier."); - } - } + return; + } - if (member.variable->datatype_specifier != nullptr) { - datatype = specified_type; + // If it's already resolving, that's ok. + if (!p_class->base_type.is_resolving()) { + Error err = resolve_class_inheritance(p_class); + if (err) { + return; + } + } - if (member.variable->initializer != nullptr) { - if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true, member.variable->initializer)) { - // Try reverse test since it can be a masked subtype. - if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true, member.variable->initializer)) { - push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer); - } 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 - parser->push_warning(member.variable->initializer, GDScriptWarning::NARROWING_CONVERSION); -#endif - } - 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) { - if (member.variable->initializer == nullptr) { - push_error(vformat(R"(Cannot infer the type of "%s" variable because there's no default value.)", member.variable->identifier->name), member.variable->identifier); - } else if (!datatype.is_set() || datatype.has_no_type()) { - push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value doesn't have a set type.)", member.variable->identifier->name), member.variable->initializer); - } else if (datatype.is_variant()) { - push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is Variant. Use explicit "Variant" type if this is intended.)", member.variable->identifier->name), member.variable->initializer); - } else if (datatype.builtin_type == Variant::NIL) { - push_error(vformat(R"(Cannot infer the type of "%s" variable because the initial value is "null".)", member.variable->identifier->name), member.variable->initializer); - } - datatype.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; - } + GDScriptParser::ClassNode *previous_class = parser->current_class; + parser->current_class = p_class; - datatype.is_constant = false; - member.variable->set_datatype(datatype); + GDScriptParser::DataType resolving_datatype; + resolving_datatype.kind = GDScriptParser::DataType::RESOLVING; + + { + switch (member.type) { + case GDScriptParser::ClassNode::Member::VARIABLE: { + check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable); + member.variable->set_datatype(resolving_datatype); + resolve_variable(member.variable, false); // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.variable->annotations) { + resolve_annotation(E); 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; - - if (member.constant->datatype_specifier != nullptr) { - specified_type = resolve_datatype(member.constant->datatype_specifier); - specified_type.is_meta_type = false; - } - - 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); - - // Can only infer typed array if it has elements. - if (array->elements.size() > 0 || (member.constant->datatype_specifier != nullptr && specified_type.has_container_element_type())) { - update_array_literal_element_type(specified_type, array); - } - } else if (member.constant->initializer->type == GDScriptParser::Node::DICTIONARY) { - const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(member.constant->initializer)); - } - - if (!member.constant->initializer->is_constant) { - push_error(R"(Initializer for a constant must be a constant expression.)", member.constant->initializer); - } - - if (member.constant->datatype_specifier != nullptr) { - datatype = specified_type; - - if (!is_type_compatible(datatype, member.constant->initializer->get_datatype(), true)) { - push_error(vformat(R"(Value of type "%s" cannot be initialized to constant of type "%s".)", member.constant->initializer->get_datatype().to_string(), datatype.to_string()), member.constant->initializer); - } else if (datatype.builtin_type == Variant::INT && member.constant->initializer->get_datatype().builtin_type == Variant::FLOAT) { -#ifdef DEBUG_ENABLED - parser->push_warning(member.constant->initializer, GDScriptWarning::NARROWING_CONVERSION); -#endif - } - } - } - datatype.is_constant = true; - - member.constant->set_datatype(datatype); + member.constant->set_datatype(resolving_datatype); + resolve_constant(member.constant, false); // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.constant->annotations) { + resolve_annotation(E); E->apply(parser, member.constant); } } break; case GDScriptParser::ClassNode::Member::SIGNAL: { check_class_member_name_conflict(p_class, member.signal->identifier->name, member.signal); + member.signal->set_datatype(resolving_datatype); + + // This is the _only_ way to declare a signal. Therefore, we can generate its + // MethodInfo inline so it's a tiny bit more efficient. + MethodInfo mi = MethodInfo(member.signal->identifier->name); + 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; - member.signal->parameters[j]->set_datatype(signal_type); + GDScriptParser::ParameterNode *param = member.signal->parameters[j]; + GDScriptParser::DataType param_type = type_from_metatype(resolve_datatype(param->datatype_specifier)); + param->set_datatype(param_type); + mi.arguments.push_back(PropertyInfo(param_type.builtin_type, param->identifier->name)); + // TODO: add signal parameter default values } - // TODO: Make MethodInfo from signal. - GDScriptParser::DataType signal_type; - signal_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - signal_type.kind = GDScriptParser::DataType::BUILTIN; - signal_type.builtin_type = Variant::SIGNAL; - - member.signal->set_datatype(signal_type); + member.signal->set_datatype(make_signal_type(mi)); // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.signal->annotations) { + resolve_annotation(E); 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; - enum_type.builtin_type = Variant::DICTIONARY; - enum_type.enum_type = member.m_enum->identifier->name; - enum_type.native_type = p_class->fqcn + "." + member.m_enum->identifier->name; - enum_type.is_meta_type = true; - enum_type.is_constant = true; + member.m_enum->set_datatype(resolving_datatype); + GDScriptParser::DataType enum_type = make_enum_type(member.m_enum->identifier->name, p_class->fqcn, true); - // Enums can't be nested, so we can safely override this. + const GDScriptParser::EnumNode *prev_enum = current_enum; current_enum = member.m_enum; + Dictionary dictionary; for (int j = 0; j < member.m_enum->values.size(); j++) { GDScriptParser::EnumNode::Value &element = member.m_enum->values.write[j]; @@ -825,27 +874,34 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } enum_type.enum_values[element.identifier->name] = element.value; + dictionary[String(element.identifier->name)] = element.value; } - current_enum = nullptr; + current_enum = prev_enum; + dictionary.set_read_only(true); member.m_enum->set_datatype(enum_type); + member.m_enum->dictionary = dictionary; // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.m_enum->annotations) { + resolve_annotation(E); E->apply(parser, member.m_enum); } } break; case GDScriptParser::ClassNode::Member::FUNCTION: - resolve_function_signature(member.function); + resolve_function_signature(member.function, p_source); break; case GDScriptParser::ClassNode::Member::ENUM_VALUE: { + member.enum_value.identifier->set_datatype(resolving_datatype); + if (member.enum_value.custom_value) { check_class_member_name_conflict(p_class, member.enum_value.identifier->name, member.enum_value.custom_value); + const GDScriptParser::EnumNode *prev_enum = current_enum; current_enum = member.enum_value.parent_enum; reduce_expression(member.enum_value.custom_value); - current_enum = nullptr; + current_enum = prev_enum; if (!member.enum_value.custom_value->is_constant) { push_error(R"(Enum values must be constant.)", member.enum_value.custom_value); @@ -859,18 +915,26 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas 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; + const GDScriptParser::EnumNode::Value &prev_value = member.enum_value.parent_enum->values[member.enum_value.index - 1]; + resolve_class_member(p_class, prev_value.identifier->name, member.enum_value.identifier); + member.enum_value.value = prev_value.value + 1; } else { member.enum_value.value = 0; } member.enum_value.resolved = true; } + // Also update the original references. - member.enum_value.parent_enum->values.write[member.enum_value.index] = member.enum_value; - p_class->members.write[i].enum_value = member.enum_value; + member.enum_value.parent_enum->values.set(member.enum_value.index, member.enum_value); + + member.enum_value.identifier->set_datatype(make_enum_type(UNNAMED_ENUM, p_class->fqcn, false)); } break; case GDScriptParser::ClassNode::Member::CLASS: check_class_member_name_conflict(p_class, member.m_class->identifier->name, member.m_class); + // If it's already resolving, that's ok. + if (!member.m_class->base_type.is_resolving()) { + resolve_class_inheritance(member.m_class, p_source); + } break; case GDScriptParser::ClassNode::Member::GROUP: // No-op, but needed to silence warnings. @@ -881,34 +945,130 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas } } - // Recurse nested classes. - for (int i = 0; i < p_class->members.size(); i++) { - GDScriptParser::ClassNode::Member member = p_class->members[i]; - if (member.type != GDScriptParser::ClassNode::Member::CLASS) { - continue; + parser->current_class = previous_class; +} + +void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source) { + if (p_source == nullptr && parser->has_class(p_class)) { + p_source = p_class; + } + + if (!p_class->resolved_interface) { + if (!parser->has_class(p_class)) { + String script_path = p_class->get_datatype().script_path; + Ref<GDScriptParserRef> parser_ref = get_parser_for(script_path); + if (parser_ref.is_null()) { + push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); + return; + } + + Error err = parser_ref->raise_status(GDScriptParserRef::PARSED); + if (err) { + push_error(vformat(R"(Could not resolve script "%s": %s.)", script_path, error_names[err]), p_source); + return; + } + + ERR_FAIL_COND_MSG(!parser_ref->get_parser()->has_class(p_class), R"(Parser bug: Mismatched external parser.)"); + + GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer(); + GDScriptParser *other_parser = parser_ref->get_parser(); + + int error_count = other_parser->errors.size(); + other_analyzer->resolve_class_interface(p_class); + if (other_parser->errors.size() > error_count) { + push_error(vformat(R"(Could not resolve class "%s".)", p_class->fqcn), p_source); + } + + return; + } + p_class->resolved_interface = true; + + if (resolve_class_inheritance(p_class) != OK) { + return; + } + + GDScriptParser::DataType base_type = p_class->base_type; + if (base_type.kind == GDScriptParser::DataType::CLASS) { + GDScriptParser::ClassNode *base_class = base_type.class_type; + resolve_class_interface(base_class, p_class); } - resolve_class_interface(member.m_class); + for (int i = 0; i < p_class->members.size(); i++) { + resolve_class_member(p_class, i); + } } +} - parser->current_class = previous_class; +void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_class, bool p_recursive) { + resolve_class_interface(p_class); + + if (p_recursive) { + for (int i = 0; i < p_class->members.size(); i++) { + GDScriptParser::ClassNode::Member member = p_class->members[i]; + if (member.type == GDScriptParser::ClassNode::Member::CLASS) { + resolve_class_interface(member.m_class, true); + } + } + } } -void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { +void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source) { + if (p_source == nullptr && parser->has_class(p_class)) { + p_source = p_class; + } + if (p_class->resolved_body) { return; } + + if (!parser->has_class(p_class)) { + String script_path = p_class->get_datatype().script_path; + Ref<GDScriptParserRef> parser_ref = get_parser_for(script_path); + if (parser_ref.is_null()) { + push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); + return; + } + + Error err = parser_ref->raise_status(GDScriptParserRef::PARSED); + if (err) { + push_error(vformat(R"(Could not resolve script "%s": %s.)", script_path, error_names[err]), p_source); + return; + } + + ERR_FAIL_COND_MSG(!parser_ref->get_parser()->has_class(p_class), R"(Parser bug: Mismatched external parser.)"); + + GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer(); + GDScriptParser *other_parser = parser_ref->get_parser(); + + int error_count = other_parser->errors.size(); + other_analyzer->resolve_class_body(p_class); + if (other_parser->errors.size() > error_count) { + push_error(vformat(R"(Could not resolve class "%s".)", p_class->fqcn), p_source); + } + + return; + } + p_class->resolved_body = true; GDScriptParser::ClassNode *previous_class = parser->current_class; parser->current_class = p_class; + resolve_class_interface(p_class, p_source); + + GDScriptParser::DataType base_type = p_class->base_type; + if (base_type.kind == GDScriptParser::DataType::CLASS) { + GDScriptParser::ClassNode *base_class = base_type.class_type; + resolve_class_body(base_class, p_class); + } + // 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) { // Apply annotations. for (GDScriptParser::AnnotationNode *&E : member.function->annotations) { + resolve_annotation(E); E->apply(parser, member.function); } @@ -945,18 +1105,6 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { } } - parser->current_class = previous_class; - - // Recurse nested classes. - for (int i = 0; i < p_class->members.size(); i++) { - GDScriptParser::ClassNode::Member member = p_class->members[i]; - if (member.type != GDScriptParser::ClassNode::Member::CLASS) { - continue; - } - - resolve_class_body(member.m_class); - } - // 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]; @@ -985,21 +1133,26 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { 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; + GDScriptParser::DataType return_datatype = getter_function->datatype; + if (getter_function->return_type != nullptr) { + return_datatype = getter_function->return_type->datatype; + return_datatype.is_meta_type = false; + } + + if (getter_function->parameters.size() != 0 || return_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, return_datatype, true)) { + push_error(vformat(R"(Function with return type "%s" cannot be used as getter for a property of type "%s".)", return_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); - } + if (member.variable->datatype.builtin_type == Variant::INT && return_datatype.builtin_type == Variant::FLOAT) { + parser->push_warning(member.variable, GDScriptWarning::NARROWING_CONVERSION); + } #endif + } } } @@ -1039,6 +1192,21 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) { } } } + + parser->current_class = previous_class; +} + +void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, bool p_recursive) { + resolve_class_body(p_class); + + if (p_recursive) { + for (int i = 0; i < p_class->members.size(); i++) { + GDScriptParser::ClassNode::Member member = p_class->members[i]; + if (member.type == GDScriptParser::ClassNode::Member::CLASS) { + resolve_class_body(member.m_class, true); + } + } + } } void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root) { @@ -1048,19 +1216,17 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root case GDScriptParser::Node::NONE: break; // Unreachable. case GDScriptParser::Node::CLASS: - resolve_class_interface(static_cast<GDScriptParser::ClassNode *>(p_node)); - resolve_class_body(static_cast<GDScriptParser::ClassNode *>(p_node)); + if (OK == resolve_class_inheritance(static_cast<GDScriptParser::ClassNode *>(p_node), true)) { + resolve_class_interface(static_cast<GDScriptParser::ClassNode *>(p_node), true); + resolve_class_body(static_cast<GDScriptParser::ClassNode *>(p_node), true); + } break; case GDScriptParser::Node::CONSTANT: - resolve_constant(static_cast<GDScriptParser::ConstantNode *>(p_node)); + resolve_constant(static_cast<GDScriptParser::ConstantNode *>(p_node), true); break; case GDScriptParser::Node::FOR: resolve_for(static_cast<GDScriptParser::ForNode *>(p_node)); break; - case GDScriptParser::Node::FUNCTION: - resolve_function_signature(static_cast<GDScriptParser::FunctionNode *>(p_node)); - resolve_function_body(static_cast<GDScriptParser::FunctionNode *>(p_node)); - break; case GDScriptParser::Node::IF: resolve_if(static_cast<GDScriptParser::IfNode *>(p_node)); break; @@ -1068,7 +1234,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root resolve_suite(static_cast<GDScriptParser::SuiteNode *>(p_node)); break; case GDScriptParser::Node::VARIABLE: - resolve_variable(static_cast<GDScriptParser::VariableNode *>(p_node)); + resolve_variable(static_cast<GDScriptParser::VariableNode *>(p_node), true); break; case GDScriptParser::Node::WHILE: resolve_while(static_cast<GDScriptParser::WhileNode *>(p_node)); @@ -1120,6 +1286,7 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root case GDScriptParser::Node::BREAKPOINT: case GDScriptParser::Node::CONTINUE: case GDScriptParser::Node::ENUM: + case GDScriptParser::Node::FUNCTION: case GDScriptParser::Node::PASS: case GDScriptParser::Node::SIGNAL: // Nothing to do. @@ -1128,10 +1295,69 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root } void GDScriptAnalyzer::resolve_annotation(GDScriptParser::AnnotationNode *p_annotation) { - // TODO: Add second validation function for annotations, so they can use checked types. + ERR_FAIL_COND_MSG(!parser->valid_annotations.has(p_annotation->name), vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name)); + + const MethodInfo &annotation_info = parser->valid_annotations[p_annotation->name].info; + + const List<PropertyInfo>::Element *E = annotation_info.arguments.front(); + for (int i = 0; i < p_annotation->arguments.size(); i++) { + GDScriptParser::ExpressionNode *argument = p_annotation->arguments[i]; + const PropertyInfo &argument_info = E->get(); + + if (E->next() != nullptr) { + E = E->next(); + } + + reduce_expression(argument); + + if (!argument->is_constant) { + push_error(vformat(R"(Argument %d of annotation "%s" isn't a constant expression.)", i + 1, p_annotation->name), argument); + return; + } + + Variant value = argument->reduced_value; + + if (value.get_type() != argument_info.type) { +#ifdef DEBUG_ENABLED + if (argument_info.type == Variant::INT && value.get_type() == Variant::FLOAT) { + parser->push_warning(argument, GDScriptWarning::NARROWING_CONVERSION); + } +#endif + + if (!Variant::can_convert_strict(value.get_type(), argument_info.type)) { + push_error(vformat(R"(Invalid argument for annotation "%s": argument %d should be "%s" but is "%s".)", p_annotation->name, i + 1, Variant::get_type_name(argument_info.type), argument->get_datatype().to_string()), argument); + return; + } + + Variant converted_to; + const Variant *converted_from = &value; + Callable::CallError call_error; + Variant::construct(argument_info.type, converted_to, &converted_from, 1, call_error); + + if (call_error.error != Callable::CallError::CALL_OK) { + push_error(vformat(R"(Cannot convert argument %d of annotation "%s" from "%s" to "%s".)", i + 1, p_annotation->name, Variant::get_type_name(value.get_type()), Variant::get_type_name(argument_info.type)), argument); + return; + } + + value = converted_to; + } + + p_annotation->resolved_arguments.push_back(value); + } } -void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *p_function) { +void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *p_function, const GDScriptParser::Node *p_source, bool p_is_lambda) { + if (p_source == nullptr) { + p_source = p_function; + } + + StringName function_name = p_function->identifier != nullptr ? p_function->identifier->name : StringName(); + + if (p_function->get_datatype().is_resolving()) { + push_error(vformat(R"(Could not resolve function "%s": Cyclic reference.)", function_name), p_source); + return; + } + if (p_function->resolved_signature) { return; } @@ -1140,6 +1366,12 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * GDScriptParser::FunctionNode *previous_function = parser->current_function; parser->current_function = p_function; + GDScriptParser::DataType prev_datatype = p_function->get_datatype(); + + GDScriptParser::DataType resolving_datatype; + resolving_datatype.kind = GDScriptParser::DataType::RESOLVING; + p_function->set_datatype(resolving_datatype); + #ifdef TOOLS_ENABLED int default_value_count = 0; #endif // TOOLS_ENABLED @@ -1148,22 +1380,24 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * resolve_parameter(p_function->parameters[i]); #ifdef DEBUG_ENABLED if (p_function->parameters[i]->usages == 0 && !String(p_function->parameters[i]->identifier->name).begins_with("_")) { - parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, p_function->identifier->name, p_function->parameters[i]->identifier->name); + parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, function_name, p_function->parameters[i]->identifier->name); } is_shadowing(p_function->parameters[i]->identifier, "function parameter"); #endif // DEBUG_ENABLED #ifdef TOOLS_ENABLED - if (p_function->parameters[i]->default_value) { + if (p_function->parameters[i]->initializer) { default_value_count++; - if (p_function->parameters[i]->default_value->is_constant) { - p_function->default_arg_values.push_back(p_function->parameters[i]->default_value->reduced_value); + if (p_function->parameters[i]->initializer->is_constant) { + p_function->default_arg_values.push_back(p_function->parameters[i]->initializer->reduced_value); + } else { + p_function->default_arg_values.push_back(Variant()); // Prevent shift. } } #endif // TOOLS_ENABLED } - if (p_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) { + if (!p_is_lambda && function_name == GDScriptLanguage::get_singleton()->strings._init) { // Constructor. GDScriptParser::DataType return_type = parser->current_class->get_datatype(); return_type.is_meta_type = false; @@ -1176,7 +1410,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * } } else { if (p_function->return_type != nullptr) { - p_function->set_datatype(resolve_datatype(p_function->return_type)); + p_function->set_datatype(type_from_metatype(resolve_datatype(p_function->return_type))); } else { // In case the function is not typed, we can safely assume it's a Variant, so it's okay to mark as "inferred" here. // It's not "undetected" to not mix up with unknown functions. @@ -1195,7 +1429,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * int default_par_count = 0; bool is_static = false; bool is_vararg = false; - if (get_function_signature(p_function, false, base_type, p_function->identifier->name, parent_return_type, parameters_types, default_par_count, is_static, is_vararg)) { + if (!p_is_lambda && get_function_signature(p_function, false, base_type, function_name, parent_return_type, parameters_types, default_par_count, is_static, is_vararg)) { bool valid = p_function->is_static == is_static; valid = valid && parent_return_type == p_function->get_datatype(); @@ -1210,11 +1444,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * if (!valid) { // Compute parent signature as a string to show in the error message. - String parent_signature = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant"; - if (parent_signature == "null") { - parent_signature = "void"; - } - parent_signature += " " + p_function->identifier->name.operator String() + "("; + String parent_signature = String(function_name) + "("; int j = 0; for (const GDScriptParser::DataType &par_type : parameters_types) { if (j > 0) { @@ -1231,17 +1461,29 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * j++; } - parent_signature += ")"; + parent_signature += ") -> "; + + const String return_type = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant"; + if (return_type == "null") { + parent_signature += "void"; + } else { + parent_signature += return_type; + } + push_error(vformat(R"(The function signature doesn't match the parent. Parent signature is "%s".)", parent_signature), p_function); } } #endif // TOOLS_ENABLED } + if (p_function->get_datatype().is_resolving()) { + p_function->set_datatype(prev_datatype); + } + parser->current_function = previous_function; } -void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_function) { +void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_function, bool p_is_lambda) { if (p_function->resolved_body) { return; } @@ -1259,7 +1501,7 @@ void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_fun return_type.type_source = GDScriptParser::DataType::INFERRED; p_function->set_datatype(p_function->body->get_datatype()); } else if (p_function->get_datatype().is_hard_type() && (p_function->get_datatype().kind != GDScriptParser::DataType::BUILTIN || p_function->get_datatype().builtin_type != Variant::NIL)) { - if (!p_function->body->has_return && p_function->identifier->name != GDScriptLanguage::get_singleton()->strings._init) { + if (!p_function->body->has_return && (p_is_lambda || p_function->identifier->name != GDScriptLanguage::get_singleton()->strings._init)) { push_error(R"(Not all code paths return a value.)", p_function); } } @@ -1297,8 +1539,10 @@ void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScript void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) { for (int i = 0; i < p_suite->statements.size(); i++) { GDScriptParser::Node *stmt = p_suite->statements[i]; - for (GDScriptParser::AnnotationNode *&annotation : stmt->annotations) { - annotation->apply(parser, stmt); + // Apply annotations. + for (GDScriptParser::AnnotationNode *&E : stmt->annotations) { + resolve_annotation(E); + E->apply(parser, stmt); } #ifdef DEBUG_ENABLED @@ -1318,6 +1562,129 @@ void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) { } } +void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assignable, const char *p_kind) { + GDScriptParser::DataType type; + type.kind = GDScriptParser::DataType::VARIANT; + + bool is_constant = p_assignable->type == GDScriptParser::Node::CONSTANT; + + GDScriptParser::DataType specified_type; + bool has_specified_type = p_assignable->datatype_specifier != nullptr; + if (has_specified_type) { + specified_type = type_from_metatype(resolve_datatype(p_assignable->datatype_specifier)); + type = specified_type; + } + + if (p_assignable->initializer != nullptr) { + reduce_expression(p_assignable->initializer); + + if (p_assignable->initializer->type == GDScriptParser::Node::ARRAY) { + GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(p_assignable->initializer); + if ((p_assignable->infer_datatype && array->elements.size() > 0) || (has_specified_type && specified_type.has_container_element_type())) { + update_array_literal_element_type(specified_type, array); + } + } + + if (is_constant) { + if (p_assignable->initializer->type == GDScriptParser::Node::ARRAY) { + const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_assignable->initializer), true); + } else if (p_assignable->initializer->type == GDScriptParser::Node::DICTIONARY) { + const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_assignable->initializer), true); + } + if (!p_assignable->initializer->is_constant) { + push_error(vformat(R"(Assigned value for %s "%s" isn't a constant expression.)", p_kind, p_assignable->identifier->name), p_assignable->initializer); + } + } + + GDScriptParser::DataType initializer_type = p_assignable->initializer->get_datatype(); + + if (p_assignable->infer_datatype) { + if (!initializer_type.is_set() || initializer_type.has_no_type() || !initializer_type.is_hard_type()) { + push_error(vformat(R"(Cannot infer the type of "%s" %s because the value doesn't have a set type.)", p_assignable->identifier->name, p_kind), p_assignable->initializer); + } else if (initializer_type.kind == GDScriptParser::DataType::BUILTIN && initializer_type.builtin_type == Variant::NIL && !is_constant) { + push_error(vformat(R"(Cannot infer the type of "%s" %s because the value is "null".)", p_assignable->identifier->name, p_kind), p_assignable->initializer); + } + } else { + if (!initializer_type.is_set()) { + push_error(vformat(R"(Could not resolve type for %s "%s".)", p_kind, p_assignable->identifier->name), p_assignable->initializer); + } + } + + if (!has_specified_type) { + type = initializer_type; + + if (!type.is_set() || (type.is_hard_type() && type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL && !is_constant)) { + type.kind = GDScriptParser::DataType::VARIANT; + } + + if (p_assignable->infer_datatype || is_constant) { + type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + } else { + type.type_source = GDScriptParser::DataType::INFERRED; + } + } else if (!specified_type.is_variant()) { + if (initializer_type.is_variant() || !initializer_type.is_hard_type()) { + mark_node_unsafe(p_assignable->initializer); + p_assignable->use_conversion_assign = true; + if (!initializer_type.is_variant() && !is_type_compatible(specified_type, initializer_type, true, p_assignable->initializer)) { + downgrade_node_type_source(p_assignable->initializer); + } + } else if (!is_type_compatible(specified_type, initializer_type, true, p_assignable->initializer)) { + if (!is_constant && is_type_compatible(initializer_type, specified_type, true, p_assignable->initializer)) { + mark_node_unsafe(p_assignable->initializer); + p_assignable->use_conversion_assign = true; + } else { + push_error(vformat(R"(Cannot assign a value of type %s to %s "%s" with specified type %s.)", initializer_type.to_string(), p_kind, p_assignable->identifier->name, specified_type.to_string()), p_assignable->initializer); + } +#ifdef DEBUG_ENABLED + } else if (specified_type.builtin_type == Variant::INT && initializer_type.builtin_type == Variant::FLOAT) { + parser->push_warning(p_assignable->initializer, GDScriptWarning::NARROWING_CONVERSION); +#endif + } + } + } + + type.is_constant = is_constant; + p_assignable->set_datatype(type); +} + +void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable, bool p_is_local) { + static constexpr const char *kind = "variable"; + resolve_assignable(p_variable, kind); + +#ifdef DEBUG_ENABLED + if (p_is_local) { + if (p_variable->usages == 0 && !String(p_variable->identifier->name).begins_with("_")) { + parser->push_warning(p_variable, GDScriptWarning::UNUSED_VARIABLE, p_variable->identifier->name); + } else if (p_variable->assignments == 0) { + parser->push_warning(p_variable, GDScriptWarning::UNASSIGNED_VARIABLE, p_variable->identifier->name); + } + + is_shadowing(p_variable->identifier, kind); + } +#endif +} + +void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant, bool p_is_local) { + static constexpr const char *kind = "constant"; + resolve_assignable(p_constant, kind); + +#ifdef DEBUG_ENABLED + if (p_is_local) { + if (p_constant->usages == 0) { + parser->push_warning(p_constant, GDScriptWarning::UNUSED_LOCAL_CONSTANT, p_constant->identifier->name); + } + + is_shadowing(p_constant->identifier, kind); + } +#endif +} + +void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parameter) { + static constexpr const char *kind = "parameter"; + resolve_assignable(p_parameter, kind); +} + void GDScriptAnalyzer::resolve_if(GDScriptParser::IfNode *p_if) { reduce_expression(p_if->condition); @@ -1377,7 +1744,7 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { if (all_is_constant) { switch (args.size()) { case 1: - reduced = args[0]; + reduced = (int32_t)args[0]; break; case 2: reduced = Vector2i(args[0], args[1]); @@ -1408,21 +1775,52 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { 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); + variable_type.builtin_type = Variant::INT; } else if (p_for->list) { resolve_node(p_for->list, false); - 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; + GDScriptParser::DataType list_type = p_for->list->get_datatype(); + if (!list_type.is_hard_type()) { + mark_node_unsafe(p_for->list); + } + if (list_type.is_variant()) { + variable_type.kind = GDScriptParser::DataType::VARIANT; + mark_node_unsafe(p_for->list); + } else if (list_type.has_container_element_type()) { + variable_type = list_type.get_container_element_type(); + variable_type.type_source = list_type.type_source; + } else if (list_type.is_typed_container_type()) { + variable_type = list_type.get_typed_container_type(); + variable_type.type_source = list_type.type_source; + } else if (list_type.builtin_type == Variant::INT || list_type.builtin_type == Variant::FLOAT || list_type.builtin_type == Variant::STRING) { + variable_type.type_source = list_type.type_source; + variable_type.kind = GDScriptParser::DataType::BUILTIN; + variable_type.builtin_type = list_type.builtin_type; + } else if (list_type.builtin_type == Variant::VECTOR2I || list_type.builtin_type == Variant::VECTOR3I) { + variable_type.type_source = list_type.type_source; + variable_type.kind = GDScriptParser::DataType::BUILTIN; + variable_type.builtin_type = Variant::INT; + } else if (list_type.builtin_type == Variant::VECTOR2 || list_type.builtin_type == Variant::VECTOR3) { + variable_type.type_source = list_type.type_source; + variable_type.kind = GDScriptParser::DataType::BUILTIN; + variable_type.builtin_type = Variant::FLOAT; + } else if (list_type.builtin_type == Variant::OBJECT) { + GDScriptParser::DataType return_type; + List<GDScriptParser::DataType> par_types; + int default_arg_count = 0; + bool is_static = false; + bool is_vararg = false; + if (get_function_signature(p_for->list, false, list_type, CoreStringNames::get_singleton()->_iter_get, return_type, par_types, default_arg_count, is_static, is_vararg)) { + variable_type = return_type; + variable_type.type_source = list_type.type_source; + } else if (!list_type.is_hard_type()) { + variable_type.kind = GDScriptParser::DataType::VARIANT; + } else { + push_error(vformat(R"(Unable to iterate on object of type "%s".)", list_type.to_string()), p_for->list); + } + } else if (list_type.builtin_type == Variant::ARRAY || list_type.builtin_type == Variant::DICTIONARY || !list_type.is_hard_type()) { variable_type.kind = GDScriptParser::DataType::VARIANT; + } else { + push_error(vformat(R"(Unable to iterate on value of type "%s".)", list_type.to_string()), p_for->list); } } if (p_for->variable) { @@ -1445,165 +1843,12 @@ void GDScriptAnalyzer::resolve_while(GDScriptParser::WhileNode *p_while) { p_while->set_datatype(p_while->loop->get_datatype()); } -void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable) { - GDScriptParser::DataType type; - type.kind = GDScriptParser::DataType::VARIANT; // By default. - - GDScriptParser::DataType specified_type; - if (p_variable->datatype_specifier != nullptr) { - specified_type = resolve_datatype(p_variable->datatype_specifier); - specified_type.is_meta_type = false; - } - - if (p_variable->initializer != nullptr) { - reduce_expression(p_variable->initializer); - if ((p_variable->infer_datatype || (p_variable->datatype_specifier != nullptr && specified_type.has_container_element_type())) && p_variable->initializer->type == GDScriptParser::Node::ARRAY) { - // Typed array. - GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(p_variable->initializer); - // Can only infer typed array if it has elements. - if ((p_variable->infer_datatype && array->elements.size() > 0) || p_variable->datatype_specifier != nullptr) { - update_array_literal_element_type(specified_type, array); - } - } - - type = p_variable->initializer->get_datatype(); - - if (p_variable->infer_datatype) { - type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; - - if (type.has_no_type()) { - push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value does not have a set type.)", p_variable->identifier->name), p_variable->initializer); - } else if (type.is_variant()) { - push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is a variant. Use explicit "Variant" type if this is intended.)", p_variable->identifier->name), p_variable->initializer); - } else if (type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL) { - push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is "null".)", p_variable->identifier->name), p_variable->initializer); - } - } else { - type.type_source = GDScriptParser::DataType::INFERRED; - } -#ifdef DEBUG_ENABLED - if (p_variable->initializer->type == GDScriptParser::Node::CALL && type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL) { - parser->push_warning(p_variable->initializer, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_variable->initializer)->function_name); - } -#endif - } - - if (p_variable->datatype_specifier != nullptr) { - type = specified_type; - type.is_meta_type = false; - - if (p_variable->initializer != nullptr) { - if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true, p_variable->initializer)) { - // Try reverse test since it can be a masked subtype. - if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true, p_variable->initializer)) { - push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer); - } 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) { - parser->push_warning(p_variable->initializer, GDScriptWarning::NARROWING_CONVERSION); -#endif - } - if (p_variable->initializer->get_datatype().is_variant() && !type.is_variant()) { - // TODO: Warn unsafe assign. - mark_node_unsafe(p_variable->initializer); - p_variable->use_conversion_assign = true; - } - } - } else if (p_variable->infer_datatype) { - if (type.has_no_type()) { - push_error(vformat(R"(Cannot infer the type of variable "%s" because the initial value doesn't have a set type.)", p_variable->identifier->name), p_variable->identifier); - } - type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; - } - - type.is_constant = false; - p_variable->set_datatype(type); - -#ifdef DEBUG_ENABLED - if (p_variable->usages == 0 && !String(p_variable->identifier->name).begins_with("_")) { - parser->push_warning(p_variable, GDScriptWarning::UNUSED_VARIABLE, p_variable->identifier->name); - } else if (p_variable->assignments == 0) { - parser->push_warning(p_variable, GDScriptWarning::UNASSIGNED_VARIABLE, p_variable->identifier->name); - } - - is_shadowing(p_variable->identifier, "variable"); -#endif -} - -void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant) { - GDScriptParser::DataType type; - - GDScriptParser::DataType explicit_type; - if (p_constant->datatype_specifier != nullptr) { - explicit_type = resolve_datatype(p_constant->datatype_specifier); - explicit_type.is_meta_type = false; - } - - if (p_constant->initializer != nullptr) { - reduce_expression(p_constant->initializer); - if (p_constant->initializer->type == GDScriptParser::Node::ARRAY) { - GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(p_constant->initializer); - const_fold_array(array); - - // Can only infer typed array if it has elements. - if (array->elements.size() > 0 || (p_constant->datatype_specifier != nullptr && explicit_type.has_container_element_type())) { - update_array_literal_element_type(explicit_type, array); - } - } else if (p_constant->initializer->type == GDScriptParser::Node::DICTIONARY) { - const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_constant->initializer)); - } - - if (!p_constant->initializer->is_constant) { - push_error(vformat(R"(Assigned value for constant "%s" isn't a constant expression.)", p_constant->identifier->name), p_constant->initializer); - } - - type = p_constant->initializer->get_datatype(); - -#ifdef DEBUG_ENABLED - if (p_constant->initializer->type == GDScriptParser::Node::CALL && type.kind == GDScriptParser::DataType::BUILTIN && type.builtin_type == Variant::NIL) { - parser->push_warning(p_constant->initializer, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_constant->initializer)->function_name); - } -#endif - } - - if (p_constant->datatype_specifier != nullptr) { - if (!is_type_compatible(explicit_type, type)) { - push_error(vformat(R"(Assigned value for constant "%s" has type %s which is not compatible with defined type %s.)", p_constant->identifier->name, type.to_string(), explicit_type.to_string()), p_constant->initializer); -#ifdef DEBUG_ENABLED - } else if (explicit_type.builtin_type == Variant::INT && type.builtin_type == Variant::FLOAT) { - parser->push_warning(p_constant->initializer, GDScriptWarning::NARROWING_CONVERSION); -#endif - } - type = explicit_type; - } else if (p_constant->infer_datatype) { - if (type.has_no_type()) { - push_error(vformat(R"(Cannot infer the type of constant "%s" because the initial value doesn't have a set type.)", p_constant->identifier->name), p_constant->identifier); - } - type.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; - } - - type.is_constant = true; - p_constant->set_datatype(type); - -#ifdef DEBUG_ENABLED - if (p_constant->usages == 0) { - parser->push_warning(p_constant, GDScriptWarning::UNUSED_LOCAL_CONSTANT, p_constant->identifier->name); - } - - is_shadowing(p_constant->identifier, "constant"); -#endif -} - void GDScriptAnalyzer::resolve_assert(GDScriptParser::AssertNode *p_assert) { reduce_expression(p_assert->condition); if (p_assert->message != nullptr) { reduce_expression(p_assert->message); - if (!p_assert->message->is_constant || p_assert->message->reduced_value.get_type() != Variant::STRING) { - push_error(R"(Expected constant string for assert error message.)", p_assert->message); + if (!p_assert->message->get_datatype().has_no_type() && (p_assert->message->get_datatype().kind != GDScriptParser::DataType::BUILTIN || p_assert->message->get_datatype().builtin_type != Variant::STRING)) { + push_error(R"(Expected string for assert error message.)", p_assert->message); } } @@ -1709,41 +1954,6 @@ void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_matc p_match_pattern->set_datatype(result); } -void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parameter) { - GDScriptParser::DataType result; - result.kind = GDScriptParser::DataType::VARIANT; - - if (p_parameter->default_value != nullptr) { - reduce_expression(p_parameter->default_value); - result = p_parameter->default_value->get_datatype(); - if (p_parameter->infer_datatype) { - result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; - } else { - result.type_source = GDScriptParser::DataType::INFERRED; - } - result.is_constant = false; - } - - if (p_parameter->datatype_specifier != nullptr) { - result = resolve_datatype(p_parameter->datatype_specifier); - result.is_meta_type = false; - - if (p_parameter->default_value != nullptr) { - if (!is_type_compatible(result, p_parameter->default_value->get_datatype())) { - push_error(vformat(R"(Type of default value for parameter "%s" (%s) is not compatible with parameter type (%s).)", p_parameter->identifier->name, p_parameter->default_value->get_datatype().to_string(), p_parameter->datatype_specifier->get_datatype().to_string()), p_parameter->default_value); - } else if (p_parameter->default_value->get_datatype().is_variant()) { - mark_node_unsafe(p_parameter); - } - } - } - - 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; @@ -1763,6 +1973,9 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) { update_array_literal_element_type(expected_type, static_cast<GDScriptParser::ArrayNode *>(p_return->return_value)); } } + if (has_expected_type && expected_type.is_hard_type() && expected_type.kind == GDScriptParser::DataType::BUILTIN && expected_type.builtin_type == Variant::NIL) { + push_error("A void function cannot return a value.", p_return); + } result = p_return->return_value->get_datatype(); } else { // Return type is null by default. @@ -1854,7 +2067,7 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre reduce_subscript(static_cast<GDScriptParser::SubscriptNode *>(p_expression)); break; case GDScriptParser::Node::TERNARY_OPERATOR: - reduce_ternary_op(static_cast<GDScriptParser::TernaryOpNode *>(p_expression)); + reduce_ternary_op(static_cast<GDScriptParser::TernaryOpNode *>(p_expression), p_is_root); break; case GDScriptParser::Node::UNARY_OPERATOR: reduce_unary_op(static_cast<GDScriptParser::UnaryOpNode *>(p_expression)); @@ -1960,6 +2173,10 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig GDScriptParser::DataType assignee_type = p_assignment->assignee->get_datatype(); + if (assignee_type.is_constant || (p_assignment->assignee->type == GDScriptParser::Node::SUBSCRIPT && static_cast<GDScriptParser::SubscriptNode *>(p_assignment->assignee)->base->is_constant)) { + push_error("Cannot assign a new value to a constant.", p_assignment->assignee); + } + // Check if assigned value is an array literal, so we can make it a typed array too if appropriate. if (assignee_type.has_container_element_type() && p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY) { update_array_literal_element_type(assignee_type, static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value)); @@ -1967,94 +2184,88 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig GDScriptParser::DataType assigned_value_type = p_assignment->assigned_value->get_datatype(); - if (assignee_type.is_constant) { - push_error("Cannot assign a new value to a constant.", p_assignment->assignee); - } - + bool assignee_is_variant = assignee_type.is_variant(); + bool assignee_is_hard = assignee_type.is_hard_type(); + bool assigned_is_variant = assigned_value_type.is_variant(); + bool assigned_is_hard = assigned_value_type.is_hard_type(); bool compatible = true; + bool downgrades_assignee = false; + bool downgrades_assigned = false; GDScriptParser::DataType op_type = assigned_value_type; - if (p_assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { + if (p_assignment->operation != GDScriptParser::AssignmentNode::OP_NONE && !op_type.is_variant()) { op_type = get_operation_type(p_assignment->variant_op, assignee_type, assigned_value_type, compatible, p_assignment->assigned_value); + + if (assignee_is_variant) { + // variant assignee + mark_node_unsafe(p_assignment); + } else if (!compatible) { + // incompatible hard types and non-variant assignee + mark_node_unsafe(p_assignment); + if (assigned_is_variant) { + // incompatible hard non-variant assignee and hard variant assigned + p_assignment->use_conversion_assign = true; + } else { + // incompatible hard non-variant types + push_error(vformat(R"(Invalid operands "%s" and "%s" for assignment operator.)", assignee_type.to_string(), assigned_value_type.to_string()), p_assignment); + } + } else if (op_type.type_source == GDScriptParser::DataType::UNDETECTED && !assigned_is_variant) { + // incompatible non-variant types (at least one weak) + downgrades_assignee = !assignee_is_hard; + downgrades_assigned = !assigned_is_hard; + } } p_assignment->set_datatype(op_type); - if (!assignee_type.is_variant() && assigned_value_type.is_hard_type()) { - if (compatible) { - compatible = is_type_compatible(assignee_type, op_type, true, p_assignment->assigned_value); - if (!compatible) { - if (assignee_type.is_hard_type()) { - // Try reverse test since it can be a masked subtype. - if (!is_type_compatible(op_type, assignee_type, true, p_assignment->assigned_value)) { - push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", assigned_value_type.to_string(), assignee_type.to_string()), p_assignment->assigned_value); - } else { - // TODO: Add warning. - mark_node_unsafe(p_assignment); + if (assignee_is_variant) { + if (!assignee_is_hard) { + // weak variant assignee + mark_node_unsafe(p_assignment); + } + } else { + if (assignee_is_hard && !assigned_is_hard) { + // hard non-variant assignee and weak assigned + mark_node_unsafe(p_assignment); + p_assignment->use_conversion_assign = true; + downgrades_assigned = downgrades_assigned || (!assigned_is_variant && !is_type_compatible(assignee_type, op_type, true, p_assignment->assigned_value)); + } else if (compatible) { + if (op_type.is_variant()) { + // non-variant assignee and variant result + mark_node_unsafe(p_assignment); + if (assignee_is_hard) { + // hard non-variant assignee and variant result + p_assignment->use_conversion_assign = true; + } else { + // weak non-variant assignee and variant result + downgrades_assignee = true; + } + } else if (!is_type_compatible(assignee_type, op_type, assignee_is_hard, p_assignment->assigned_value)) { + // non-variant assignee and incompatible result + mark_node_unsafe(p_assignment); + if (assignee_is_hard) { + if (is_type_compatible(op_type, assignee_type, true, p_assignment->assigned_value)) { + // hard non-variant assignee and maybe compatible result p_assignment->use_conversion_assign = true; + } else { + // hard non-variant assignee and incompatible result + push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", assigned_value_type.to_string(), assignee_type.to_string()), p_assignment->assigned_value); } } else { - // TODO: Warning in this case. - mark_node_unsafe(p_assignment); + // weak non-variant assignee and incompatible result + downgrades_assignee = true; } } - } else { - push_error(vformat(R"(Invalid operands "%s" and "%s" for assignment operator.)", assignee_type.to_string(), assigned_value_type.to_string()), p_assignment); } } - if (assignee_type.has_no_type() || assigned_value_type.is_variant()) { - mark_node_unsafe(p_assignment); - if (assignee_type.is_hard_type() && !assignee_type.is_variant()) { - p_assignment->use_conversion_assign = true; - } + if (downgrades_assignee) { + downgrade_node_type_source(p_assignment->assignee); } - - if (p_assignment->assignee->type == GDScriptParser::Node::IDENTIFIER) { - // Change source type so it's not wrongly detected later. - GDScriptParser::IdentifierNode *identifier = static_cast<GDScriptParser::IdentifierNode *>(p_assignment->assignee); - - switch (identifier->source) { - case GDScriptParser::IdentifierNode::MEMBER_VARIABLE: { - GDScriptParser::DataType id_type = identifier->variable_source->get_datatype(); - if (!id_type.is_hard_type()) { - id_type.kind = GDScriptParser::DataType::VARIANT; - id_type.type_source = GDScriptParser::DataType::UNDETECTED; - 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.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.kind = GDScriptParser::DataType::VARIANT; - id_type.type_source = GDScriptParser::DataType::UNDETECTED; - identifier->variable_source->set_datatype(id_type); - } - } break; - default: - // Nothing to do. - break; - } + if (downgrades_assigned) { + downgrade_node_type_source(p_assignment->assigned_value); } #ifdef DEBUG_ENABLED - if (p_assignment->assigned_value->type == GDScriptParser::Node::CALL && assigned_value_type.kind == GDScriptParser::DataType::BUILTIN && assigned_value_type.builtin_type == Variant::NIL) { - parser->push_warning(p_assignment->assigned_value, GDScriptWarning::VOID_ASSIGNMENT, static_cast<GDScriptParser::CallNode *>(p_assignment->assigned_value)->function_name); - } else if (assignee_type.is_hard_type() && assignee_type.builtin_type == Variant::INT && assigned_value_type.builtin_type == Variant::FLOAT) { + if (assignee_type.is_hard_type() && assignee_type.builtin_type == Variant::INT && assigned_value_type.builtin_type == Variant::FLOAT) { parser->push_warning(p_assignment->assigned_value, GDScriptWarning::NARROWING_CONVERSION); } #endif @@ -2147,7 +2358,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o GDScriptParser::DataType test_type = right_type; test_type.is_meta_type = false; - if (!is_type_compatible(test_type, p_binary_op->left_operand->get_datatype(), false)) { + if (!is_type_compatible(test_type, left_type, false)) { push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)"), p_binary_op->left_operand); p_binary_op->reduced_value = false; } else { @@ -2181,11 +2392,11 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o GDScriptParser::DataType test_type = right_type; test_type.is_meta_type = false; - if (!is_type_compatible(test_type, p_binary_op->left_operand->get_datatype(), false)) { + if (!is_type_compatible(test_type, left_type, false)) { // Test reverse as well to consider for subtypes. - if (!is_type_compatible(p_binary_op->left_operand->get_datatype(), test_type, false)) { - if (p_binary_op->left_operand->get_datatype().is_hard_type()) { - push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", p_binary_op->left_operand->get_datatype().to_string(), test_type.to_string()), p_binary_op->left_operand); + if (!is_type_compatible(left_type, test_type, false)) { + if (left_type.is_hard_type()) { + push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", left_type.to_string(), test_type.to_string()), p_binary_op->left_operand); } else { // TODO: Warning. mark_node_unsafe(p_binary_op); @@ -2271,7 +2482,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a switch (err.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: - push_error(vformat(R"(Invalid argument for %s constructor: argument %d should be %s but is %s.)", Variant::get_type_name(builtin_type), err.argument + 1, + push_error(vformat(R"(Invalid argument for %s constructor: argument %d should be "%s" but is "%s".)", Variant::get_type_name(builtin_type), err.argument + 1, Variant::get_type_name(Variant::Type(err.expected)), p_call->arguments[err.argument]->get_datatype().to_string()), p_call->arguments[err.argument]); break; @@ -2331,7 +2542,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a bool types_match = true; for (int i = 0; i < p_call->arguments.size(); i++) { - GDScriptParser::DataType par_type = type_from_property(info.arguments[i]); + GDScriptParser::DataType par_type = type_from_property(info.arguments[i], true); if (!is_type_compatible(par_type, p_call->arguments[i]->get_datatype(), true)) { types_match = false; @@ -2369,6 +2580,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } else if (GDScriptUtilityFunctions::function_exists(function_name)) { MethodInfo function_info = GDScriptUtilityFunctions::get_function_info(function_name); + if (!p_is_root && !p_is_await && function_info.return_val.type == Variant::NIL && ((function_info.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT) == 0)) { + push_error(vformat(R"*(Cannot get return value of call to "%s()" because it returns "void".)*", function_name), p_call); + } + if (all_is_constant && GDScriptUtilityFunctions::is_function_constant(function_name)) { // Can call on compilation. Vector<const Variant *> args; @@ -2383,8 +2598,8 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a switch (err.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { PropertyInfo wrong_arg = function_info.arguments[err.argument]; - push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", function_name, err.argument + 1, - type_from_property(wrong_arg).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()), + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1, + type_from_property(wrong_arg, true).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()), p_call->arguments[err.argument]); } break; case Callable::CallError::CALL_ERROR_INVALID_METHOD: @@ -2412,6 +2627,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } else if (Variant::has_utility_function(function_name)) { MethodInfo function_info = info_from_utility_func(function_name); + if (!p_is_root && !p_is_await && function_info.return_val.type == Variant::NIL && ((function_info.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT) == 0)) { + push_error(vformat(R"*(Cannot get return value of call to "%s()" because it returns "void".)*", function_name), p_call); + } + if (all_is_constant && Variant::get_utility_function_type(function_name) == Variant::UTILITY_FUNC_TYPE_MATH) { // Can call on compilation. Vector<const Variant *> args; @@ -2425,9 +2644,15 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a switch (err.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { - PropertyInfo wrong_arg = function_info.arguments[err.argument]; - push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", function_name, err.argument + 1, - type_from_property(wrong_arg).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()), + String expected_type_name; + if (err.argument < function_info.arguments.size()) { + expected_type_name = type_from_property(function_info.arguments[err.argument], true).to_string(); + } else { + expected_type_name = Variant::get_type_name((Variant::Type)err.expected); + } + + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", function_name, err.argument + 1, + expected_type_name, p_call->arguments[err.argument]->get_datatype().to_string()), p_call->arguments[err.argument]); } break; case Callable::CallError::CALL_ERROR_INVALID_METHOD: @@ -2502,6 +2727,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } else { reduce_expression(subscript->base); base_type = subscript->base->get_datatype(); + is_self = subscript->base->type == GDScriptParser::Node::SELF; } } else { // Invalid call. Error already sent in parser. @@ -2548,13 +2774,23 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a mark_lambda_use_self(); } + if (!p_is_root && !p_is_await && return_type.is_hard_type() && return_type.kind == GDScriptParser::DataType::BUILTIN && return_type.builtin_type == Variant::NIL) { + push_error(vformat(R"*(Cannot get return value of call to "%s()" because it returns "void".)*", p_call->function_name), p_call); + } + #ifdef DEBUG_ENABLED if (p_is_root && return_type.kind != GDScriptParser::DataType::UNRESOLVED && return_type.builtin_type != Variant::NIL) { parser->push_warning(p_call, GDScriptWarning::RETURN_VALUE_DISCARDED, p_call->function_name); } - if (is_static && !base_type.is_meta_type && !(callee_type != GDScriptParser::Node::SUBSCRIPT && parser->current_function != nullptr && parser->current_function->is_static)) { - parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, base_type.to_string()); + if (is_static && !base_type.is_meta_type && !(is_self && parser->current_function != nullptr && parser->current_function->is_static)) { + String caller_type = String(base_type.native_type); + + if (caller_type.is_empty()) { + caller_type = base_type.to_string(); + } + + parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, caller_type); } #endif // DEBUG_ENABLED @@ -2562,8 +2798,10 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a } else { bool found = false; - // Check if the name exists as something else. - if (!p_call->is_super && callee_type != GDScriptParser::Node::NONE) { + // Enums do not have functions other than the built-in dictionary ones. + if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) { + push_error(vformat(R"*(Enums only have Dictionary built-in methods. Function "%s()" does not exist for enum "%s".)*", p_call->function_name, base_type.enum_type), p_call->callee); + } else if (!p_call->is_super && callee_type != GDScriptParser::Node::NONE) { // Check if the name exists as something else. GDScriptParser::IdentifierNode *callee_id; if (callee_type == GDScriptParser::Node::IDENTIFIER) { callee_id = static_cast<GDScriptParser::IdentifierNode *>(p_call->callee); @@ -2593,7 +2831,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string(); push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee); } else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::NATIVE && base_type.is_meta_type)) { - push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type.operator String()), p_call); + push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type), p_call); } } @@ -2607,33 +2845,61 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { reduce_expression(p_cast->operand); - GDScriptParser::DataType cast_type = resolve_datatype(p_cast->cast_type); + GDScriptParser::DataType cast_type = type_from_metatype(resolve_datatype(p_cast->cast_type)); if (!cast_type.is_set()) { mark_node_unsafe(p_cast); return; } - cast_type = type_from_metatype(cast_type); // The casted value won't be a type name. p_cast->set_datatype(cast_type); if (!cast_type.is_variant()) { GDScriptParser::DataType op_type = p_cast->operand->get_datatype(); if (!op_type.is_variant()) { bool valid = false; + bool more_informative_error = false; if (op_type.kind == GDScriptParser::DataType::ENUM && cast_type.kind == GDScriptParser::DataType::ENUM) { - // Enum types are compatible between each other, so it's a safe cast. - valid = true; + // Enum casts are compatible when value from operand exists in target enum + if (p_cast->operand->is_constant && p_cast->operand->reduced) { + if (enum_get_value_name(cast_type, p_cast->operand->reduced_value) != StringName()) { + valid = true; + } else { + valid = false; + more_informative_error = true; + push_error(vformat(R"(Invalid cast. Enum "%s" does not have value corresponding to "%s.%s" (%d).)", + cast_type.to_string(), op_type.enum_type, + enum_get_value_name(op_type, p_cast->operand->reduced_value), // Can never be null + p_cast->operand->reduced_value.operator uint64_t()), + p_cast->cast_type); + } + } else { + // Can't statically tell whether int has a corresponding enum value. Valid but dangerous! + mark_node_unsafe(p_cast); + valid = true; + } } else if (op_type.kind == GDScriptParser::DataType::BUILTIN && op_type.builtin_type == Variant::INT && cast_type.kind == GDScriptParser::DataType::ENUM) { - // Convertint int to enum is always valid. - valid = true; + // Int assignment to enum not valid when exact int assigned is known but is not an enum value + if (p_cast->operand->is_constant && p_cast->operand->reduced) { + if (enum_get_value_name(cast_type, p_cast->operand->reduced_value) != StringName()) { + valid = true; + } else { + valid = false; + more_informative_error = true; + push_error(vformat(R"(Invalid cast. Enum "%s" does not have enum value %d.)", cast_type.to_string(), p_cast->operand->reduced_value.operator uint64_t()), p_cast->cast_type); + } + } else { + // Can't statically tell whether int has a corresponding enum value. Valid but dangerous! + mark_node_unsafe(p_cast); + valid = true; + } } else if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) { valid = Variant::can_convert(op_type.builtin_type, cast_type.builtin_type); } else if (op_type.kind != GDScriptParser::DataType::BUILTIN && cast_type.kind != GDScriptParser::DataType::BUILTIN) { valid = is_type_compatible(cast_type, op_type) || is_type_compatible(op_type, cast_type); } - if (!valid) { + if (!valid && !more_informative_error) { push_error(vformat(R"(Invalid cast. Cannot convert from "%s" to "%s".)", op_type.to_string(), cast_type.to_string()), p_cast->cast_type); } } @@ -2651,7 +2917,7 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { } void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) { - HashMap<Variant, GDScriptParser::ExpressionNode *, VariantHasher, VariantComparator> elements; + HashMap<Variant, GDScriptParser::ExpressionNode *, VariantHasher, StringLikeVariantComparator> elements; for (int i = 0; i < p_dictionary->elements.size(); i++) { const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; @@ -2709,7 +2975,7 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str return type; } - Error err = ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + Error err = ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); if (err) { push_error(vformat(R"(Could not resolve class "%s", because of a parser error.)", p_class_name), p_source); type.type_source = GDScriptParser::DataType::UNDETECTED; @@ -2717,29 +2983,29 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str return type; } - type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - type.kind = GDScriptParser::DataType::CLASS; - type.builtin_type = Variant::OBJECT; - type.native_type = ScriptServer::get_global_class_native_base(p_class_name); - type.class_type = ref->get_parser()->head; - type.script_path = ref->get_parser()->script_path; - type.is_constant = true; - type.is_meta_type = true; - return type; + return ref->get_parser()->head->get_datatype(); } else { - type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - type.kind = GDScriptParser::DataType::SCRIPT; - type.builtin_type = Variant::OBJECT; - type.native_type = ScriptServer::get_global_class_native_base(p_class_name); - type.script_type = ResourceLoader::load(path, "Script"); - type.script_path = path; - type.is_constant = true; - type.is_meta_type = true; - return type; + return make_script_meta_type(ResourceLoader::load(path, "Script")); } } +void GDScriptAnalyzer::reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype) { + ERR_FAIL_NULL(p_identifier); + + p_identifier->set_datatype(p_identifier_datatype); + Error err = OK; + GDScript *scr = GDScriptCache::get_shallow_script(p_identifier_datatype.script_path, err).ptr(); + ERR_FAIL_COND_MSG(err != OK, vformat(R"(Error while getting cache for script "%s".)", p_identifier_datatype.script_path)); + scr = scr->find_class(p_identifier_datatype.class_type->fqcn); + p_identifier->reduced_value = scr; + p_identifier->is_constant = true; +} + void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType *p_base) { + if (!p_identifier->get_datatype().has_no_type()) { + return; + } + GDScriptParser::DataType base; if (p_base == nullptr) { base = type_from_metatype(parser->current_class->get_datatype()); @@ -2752,26 +3018,14 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod if (base.kind == GDScriptParser::DataType::ENUM) { if (base.is_meta_type) { if (base.enum_values.has(name)) { + p_identifier->set_datatype(type_from_metatype(base)); p_identifier->is_constant = true; p_identifier->reduced_value = base.enum_values[name]; - - GDScriptParser::DataType result; - result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - result.kind = GDScriptParser::DataType::ENUM; - result.is_constant = true; - result.builtin_type = Variant::INT; - result.native_type = base.native_type; - result.enum_type = base.enum_type; - result.enum_values = base.enum_values; - p_identifier->set_datatype(result); return; - } else { - // Consider as a Dictionary, so it can be anything. - // This will be evaluated in the next if block. - base.kind = GDScriptParser::DataType::BUILTIN; - base.builtin_type = Variant::DICTIONARY; - base.is_meta_type = false; } + + // Enum does not have this value, return. + return; } else { push_error(R"(Cannot get property from enum value.)", p_identifier); return; @@ -2825,93 +3079,94 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } GDScriptParser::ClassNode *base_class = base.class_type; + List<GDScriptParser::ClassNode *> script_classes; + bool is_base = true; - // TODO: Switch current class/function/suite here to avoid misrepresenting identifiers (in recursive reduce calls). - while (base_class != nullptr) { - if (base_class->identifier && base_class->identifier->name == name) { - p_identifier->set_datatype(base_class->get_datatype()); + if (base_class != nullptr) { + get_class_node_current_scope_classes(base_class, &script_classes); + } + + for (GDScriptParser::ClassNode *script_class : script_classes) { + if (p_base == nullptr && script_class->identifier && script_class->identifier->name == name) { + reduce_identifier_from_base_set_class(p_identifier, script_class->get_datatype()); return; } - if (base_class->has_member(name)) { - const GDScriptParser::ClassNode::Member &member = base_class->get_member(name); - p_identifier->set_datatype(member.get_datatype()); + + if (script_class->has_member(name)) { + resolve_class_member(script_class, name, p_identifier); + + GDScriptParser::ClassNode::Member member = script_class->get_member(name); switch (member.type) { - case GDScriptParser::ClassNode::Member::CONSTANT: - // For out-of-order resolution: - reduce_expression(member.constant->initializer); + case GDScriptParser::ClassNode::Member::CONSTANT: { + p_identifier->set_datatype(member.get_datatype()); p_identifier->is_constant = true; p_identifier->reduced_value = member.constant->initializer->reduced_value; - p_identifier->set_datatype(member.constant->initializer->get_datatype()); p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; p_identifier->constant_source = member.constant; - break; - case GDScriptParser::ClassNode::Member::ENUM_VALUE: + return; + } + + 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; p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; - break; - 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. - } - return; - } - // Check outer constants. - // TODO: Allow outer static functions. - GDScriptParser::ClassNode *outer = base_class->outer; - while (outer != nullptr) { - if (outer->has_member(name)) { - const GDScriptParser::ClassNode::Member &member = outer->get_member(name); - 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: { + return; + } + + case GDScriptParser::ClassNode::Member::ENUM: { + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.m_enum->dictionary; + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; + return; + } + + case GDScriptParser::ClassNode::Member::VARIABLE: { + if (is_base && !base.is_meta_type) { p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = true; - p_identifier->reduced_value = member.enum_value.value; + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE; + p_identifier->variable_source = member.variable; + member.variable->usages += 1; return; - } break; - case GDScriptParser::ClassNode::Member::ENUM: { + } + } break; + + case GDScriptParser::ClassNode::Member::SIGNAL: { + if (is_base && !base.is_meta_type) { p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = false; + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_SIGNAL; return; - } break; - case GDScriptParser::ClassNode::Member::CLASS: { - resolve_class_interface(member.m_class); - p_identifier->set_datatype(member.m_class->get_datatype()); + } + } break; + + case GDScriptParser::ClassNode::Member::FUNCTION: { + if (is_base && !base.is_meta_type) { + p_identifier->set_datatype(make_callable_type(member.function->info)); return; - } break; - default: - break; + } + } break; + + case GDScriptParser::ClassNode::Member::CLASS: { + reduce_identifier_from_base_set_class(p_identifier, member.get_datatype()); + return; + } + + default: { + // Do nothing } } - outer = outer->outer; } - base_class = base_class->base_type.class_type; + if (is_base) { + is_base = script_class->base_type.class_type != nullptr; + if (!is_base && p_base != nullptr) { + break; + } + } } - // Check native members. + // Check native members. No need for native class recursion because Node exposes all Object's properties. const StringName &native = base.native_type; if (class_exists(native)) { @@ -2938,35 +3193,39 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod return; } if (ClassDB::has_enum(native, name)) { - p_identifier->set_datatype(make_native_enum_type(native, name)); + p_identifier->set_datatype(make_native_enum_type(name, native)); p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; return; } bool valid = false; + int64_t int_constant = ClassDB::get_integer_constant(native, name, &valid); if (valid) { p_identifier->is_constant = true; p_identifier->reduced_value = int_constant; - p_identifier->set_datatype(type_from_variant(int_constant, p_identifier)); p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_CONSTANT; - return; + + // Check whether this constant, which exists, belongs to an enum + StringName enum_name = ClassDB::get_integer_constant_enum(native, name); + if (enum_name != StringName()) { + p_identifier->set_datatype(make_native_enum_type(enum_name, native, false)); + } else { + p_identifier->set_datatype(type_from_variant(int_constant, p_identifier)); + } } } } void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_identifier, bool can_be_builtin) { - // TODO: This is opportunity to further infer types. + // TODO: This is an opportunity to further infer types. - // Check if we are inside and enum. This allows enum values to access other elements of the same enum. + // Check if we are inside an enum. This allows enum values to access other elements of the same enum. if (current_enum) { for (int i = 0; i < current_enum->values.size(); i++) { const GDScriptParser::EnumNode::Value &element = current_enum->values[i]; if (element.identifier->name == p_identifier->name) { - GDScriptParser::DataType type; - type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM : GDScriptParser::DataType::BUILTIN; - type.builtin_type = Variant::INT; - type.is_constant = true; + StringName enum_name = current_enum->identifier ? current_enum->identifier->name : UNNAMED_ENUM; + GDScriptParser::DataType type = make_enum_type(enum_name, parser->current_class->fqcn, false); if (element.parent_enum->identifier) { type.enum_type = element.parent_enum->identifier->name; } @@ -2999,6 +3258,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident p_identifier->reduced_value = p_identifier->constant_source->initializer->reduced_value; found_source = true; break; + case GDScriptParser::IdentifierNode::MEMBER_SIGNAL: case GDScriptParser::IdentifierNode::INHERITED_VARIABLE: mark_lambda_use_self(); break; @@ -3034,18 +3294,20 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident } if (found_source) { - if ((p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE) && parser->current_function && parser->current_function->is_static) { + bool source_is_variable = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE; + bool source_is_signal = p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_SIGNAL; + if ((source_is_variable || source_is_signal) && parser->current_function && parser->current_function->is_static) { // Get the parent function above any lambda. GDScriptParser::FunctionNode *parent_function = parser->current_function; while (parent_function->source_lambda) { parent_function = parent_function->source_lambda->parent_function; } - push_error(vformat(R"*(Cannot access instance variable "%s" from the static function "%s()".)*", p_identifier->name, parent_function->identifier->name), p_identifier); + push_error(vformat(R"*(Cannot access %s "%s" from the static function "%s()".)*", source_is_signal ? "signal" : "instance variable", p_identifier->name, parent_function->identifier->name), p_identifier); } if (!lambda_stack.is_empty()) { - // If the identifier is a member variable (including the native class properties), we consider the lambda to be using `self`, so we keep a reference to the current instance. - if (p_identifier->source == GDScriptParser::IdentifierNode::MEMBER_VARIABLE || p_identifier->source == GDScriptParser::IdentifierNode::INHERITED_VARIABLE) { + // If the identifier is a member variable (including the native class properties) or a signal, we consider the lambda to be using `self`, so we keep a reference to the current instance. + if (source_is_variable || source_is_signal) { mark_lambda_use_self(); return; // No need to capture. } @@ -3102,14 +3364,31 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident GDScriptParser::DataType result; result.kind = GDScriptParser::DataType::NATIVE; result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - if (autoload.path.to_lower().ends_with(GDScriptLanguage::get_singleton()->get_extension())) { + if (ResourceLoader::get_resource_type(autoload.path) == "GDScript") { Ref<GDScriptParserRef> singl_parser = get_parser_for(autoload.path); if (singl_parser.is_valid()) { - Error err = singl_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + Error err = singl_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); if (err == OK) { result = type_from_metatype(singl_parser->get_parser()->head->get_datatype()); } } + } else if (ResourceLoader::get_resource_type(autoload.path) == "PackedScene") { + if (GDScriptLanguage::get_singleton()->has_any_global_constant(name)) { + Variant constant = GDScriptLanguage::get_singleton()->get_any_global_constant(name); + Node *node = Object::cast_to<Node>(constant); + if (node != nullptr) { + Ref<GDScript> scr = node->get_script(); + if (scr.is_valid()) { + Ref<GDScriptParserRef> singl_parser = get_parser_for(scr->get_script_path()); + if (singl_parser.is_valid()) { + Error err = singl_parser->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + if (err == OK) { + result = type_from_metatype(singl_parser->get_parser()->head->get_datatype()); + } + } + } + } + } } result.is_constant = true; p_identifier->set_datatype(result); @@ -3117,17 +3396,8 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident } } - if (GDScriptLanguage::get_singleton()->get_global_map().has(name)) { - int idx = GDScriptLanguage::get_singleton()->get_global_map()[name]; - Variant constant = GDScriptLanguage::get_singleton()->get_global_array()[idx]; - p_identifier->set_datatype(type_from_variant(constant, p_identifier)); - p_identifier->is_constant = true; - p_identifier->reduced_value = constant; - return; - } - - if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(name)) { - Variant constant = GDScriptLanguage::get_singleton()->get_named_globals_map()[name]; + if (GDScriptLanguage::get_singleton()->has_any_global_constant(name)) { + Variant constant = GDScriptLanguage::get_singleton()->get_any_global_constant(name); p_identifier->set_datatype(type_from_variant(constant, p_identifier)); p_identifier->is_constant = true; p_identifier->reduced_value = constant; @@ -3158,16 +3428,10 @@ void GDScriptAnalyzer::reduce_lambda(GDScriptParser::LambdaNode *p_lambda) { return; } - GDScriptParser::FunctionNode *previous_function = parser->current_function; - parser->current_function = p_lambda->function; - lambda_stack.push_back(p_lambda); - - for (int i = 0; i < p_lambda->function->parameters.size(); i++) { - resolve_parameter(p_lambda->function->parameters[i]); - } - - resolve_suite(p_lambda->function->body); + resolve_function_signature(p_lambda->function, p_lambda, true); + resolve_function_body(p_lambda->function, true); + lambda_stack.pop_back(); int captures_amount = p_lambda->captures.size(); if (captures_amount > 0) { @@ -3192,9 +3456,6 @@ void GDScriptAnalyzer::reduce_lambda(GDScriptParser::LambdaNode *p_lambda) { p_lambda->function->parameters_indices[capture->name] = i; } } - - lambda_stack.pop_back(); - parser->current_function = previous_function; } void GDScriptAnalyzer::reduce_literal(GDScriptParser::LiteralNode *p_literal) { @@ -3235,9 +3496,28 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { } } else { // 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 preload resource file "%s".)", p_preload->resolved_path), p_preload->path); + + // Must load GDScript and PackedScenes separately to permit cyclic references + // as ResourceLoader::load() detect and reject those. + if (ResourceLoader::get_resource_type(p_preload->resolved_path) == "GDScript") { + Error err = OK; + Ref<GDScript> res = GDScriptCache::get_shallow_script(p_preload->resolved_path, err, parser->script_path); + p_preload->resource = res; + if (err != OK) { + push_error(vformat(R"(Could not preload resource script "%s".)", p_preload->resolved_path), p_preload->path); + } + } else if (ResourceLoader::get_resource_type(p_preload->resolved_path) == "PackedScene") { + Error err = OK; + Ref<PackedScene> res = GDScriptCache::get_packed_scene(p_preload->resolved_path, err, parser->script_path); + p_preload->resource = res; + if (err != OK) { + push_error(vformat(R"(Could not preload resource scene "%s".)", p_preload->resolved_path), p_preload->path); + } + } else { + p_preload->resource = ResourceLoader::load(p_preload->resolved_path); + if (p_preload->resource.is_null()) { + push_error(vformat(R"(Could not preload resource file "%s".)", p_preload->resolved_path), p_preload->path); + } } } } @@ -3263,9 +3543,9 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri reduce_expression(p_subscript->base); if (p_subscript->base->type == GDScriptParser::Node::ARRAY) { - const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_subscript->base)); + const_fold_array(static_cast<GDScriptParser::ArrayNode *>(p_subscript->base), false); } else if (p_subscript->base->type == GDScriptParser::Node::DICTIONARY) { - const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_subscript->base)); + const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(p_subscript->base), false); } } @@ -3275,43 +3555,45 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri if (p_subscript->attribute == nullptr) { return; } - if (p_subscript->base->is_constant) { + + GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); + bool valid = false; + // If the base is a metatype, use the analyzer instead. + if (p_subscript->base->is_constant && !base_type.is_meta_type) { // Just try to get it. - bool valid = false; Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid); - if (!valid) { - push_error(vformat(R"(Cannot get member "%s" from "%s".)", p_subscript->attribute->name, p_subscript->base->reduced_value), p_subscript->index); - result_type.kind = GDScriptParser::DataType::VARIANT; - } else { + if (valid) { p_subscript->is_constant = true; p_subscript->reduced_value = value; result_type = type_from_variant(value, p_subscript); } + } else if (base_type.is_variant() || !base_type.is_hard_type()) { + valid = true; + result_type.kind = GDScriptParser::DataType::VARIANT; + mark_node_unsafe(p_subscript); } else { - GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); - - if (base_type.is_variant() || !base_type.is_hard_type()) { - result_type.kind = GDScriptParser::DataType::VARIANT; - mark_node_unsafe(p_subscript); - } else { - reduce_identifier_from_base(p_subscript->attribute, &base_type); - GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype(); - if (attr_type.is_set()) { - result_type = attr_type; - p_subscript->is_constant = p_subscript->attribute->is_constant; - p_subscript->reduced_value = p_subscript->attribute->reduced_value; - } else { - if (base_type.kind == GDScriptParser::DataType::BUILTIN) { - push_error(vformat(R"(Cannot find member "%s" in base "%s".)", p_subscript->attribute->name, base_type.to_string()), p_subscript->attribute); + reduce_identifier_from_base(p_subscript->attribute, &base_type); + GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype(); + if (attr_type.is_set()) { + valid = true; + result_type = attr_type; + p_subscript->is_constant = p_subscript->attribute->is_constant; + p_subscript->reduced_value = p_subscript->attribute->reduced_value; + } else if (!base_type.is_meta_type || !base_type.is_constant) { + valid = base_type.kind != GDScriptParser::DataType::BUILTIN; #ifdef DEBUG_ENABLED - } else { - parser->push_warning(p_subscript, GDScriptWarning::UNSAFE_PROPERTY_ACCESS, p_subscript->attribute->name, base_type.to_string()); -#endif - } - result_type.kind = GDScriptParser::DataType::VARIANT; + if (valid) { + parser->push_warning(p_subscript, GDScriptWarning::UNSAFE_PROPERTY_ACCESS, p_subscript->attribute->name, base_type.to_string()); } +#endif + result_type.kind = GDScriptParser::DataType::VARIANT; + mark_node_unsafe(p_subscript); } } + if (!valid) { + push_error(vformat(R"(Cannot find member "%s" in base "%s".)", p_subscript->attribute->name, type_from_metatype(base_type).to_string()), p_subscript->attribute); + result_type.kind = GDScriptParser::DataType::VARIANT; + } } else { if (p_subscript->index == nullptr) { return; @@ -3324,12 +3606,12 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri Variant value = p_subscript->base->reduced_value.get(p_subscript->index->reduced_value, &valid); if (!valid) { push_error(vformat(R"(Cannot get index "%s" from "%s".)", p_subscript->index->reduced_value, p_subscript->base->reduced_value), p_subscript->index); + result_type.kind = GDScriptParser::DataType::VARIANT; } else { p_subscript->is_constant = true; p_subscript->reduced_value = value; result_type = type_from_variant(value, p_subscript); } - result_type.kind = GDScriptParser::DataType::VARIANT; } else { GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); GDScriptParser::DataType index_type = p_subscript->index->get_datatype(); @@ -3364,7 +3646,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::QUATERNION: case Variant::AABB: case Variant::OBJECT: - error = index_type.builtin_type != Variant::STRING; + error = index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME; break; // Expect String or number. case Variant::BASIS: @@ -3378,11 +3660,11 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::TRANSFORM3D: case Variant::PROJECTION: error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT && - index_type.builtin_type != Variant::STRING; + index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME; break; // Expect String or int. case Variant::COLOR: - error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING; + error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME; break; // Don't support indexing, but we will check it later. case Variant::RID: @@ -3507,10 +3789,10 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri p_subscript->set_datatype(result_type); } -void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op) { +void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op, bool p_is_root) { reduce_expression(p_ternary_op->condition); - reduce_expression(p_ternary_op->true_expr); - reduce_expression(p_ternary_op->false_expr); + reduce_expression(p_ternary_op->true_expr, p_is_root); + reduce_expression(p_ternary_op->false_expr, p_is_root); GDScriptParser::DataType result; @@ -3566,39 +3848,40 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) return; } + GDScriptParser::DataType operand_type = p_unary_op->operand->get_datatype(); + if (p_unary_op->operand->is_constant) { p_unary_op->is_constant = true; p_unary_op->reduced_value = Variant::evaluate(p_unary_op->variant_op, p_unary_op->operand->reduced_value, Variant()); result = type_from_variant(p_unary_op->reduced_value, p_unary_op); - } else if (p_unary_op->operand->get_datatype().is_variant()) { + } + + if (operand_type.is_variant()) { result.kind = GDScriptParser::DataType::VARIANT; mark_node_unsafe(p_unary_op); } else { bool valid = false; - result = get_operation_type(p_unary_op->variant_op, p_unary_op->operand->get_datatype(), valid, p_unary_op); + result = get_operation_type(p_unary_op->variant_op, operand_type, valid, p_unary_op); if (!valid) { - push_error(vformat(R"(Invalid operand of type "%s" for unary operator "%s".)", p_unary_op->operand->get_datatype().to_string(), Variant::get_operator_name(p_unary_op->variant_op)), p_unary_op->operand); + push_error(vformat(R"(Invalid operand of type "%s" for unary operator "%s".)", operand_type.to_string(), Variant::get_operator_name(p_unary_op->variant_op)), p_unary_op); } } p_unary_op->set_datatype(result); } -void GDScriptAnalyzer::const_fold_array(GDScriptParser::ArrayNode *p_array) { - bool all_is_constant = true; - +void GDScriptAnalyzer::const_fold_array(GDScriptParser::ArrayNode *p_array, bool p_is_const) { for (int i = 0; i < p_array->elements.size(); i++) { GDScriptParser::ExpressionNode *element = p_array->elements[i]; if (element->type == GDScriptParser::Node::ARRAY) { - const_fold_array(static_cast<GDScriptParser::ArrayNode *>(element)); + const_fold_array(static_cast<GDScriptParser::ArrayNode *>(element), p_is_const); } else if (element->type == GDScriptParser::Node::DICTIONARY) { - const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(element)); + const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(element), p_is_const); } - all_is_constant = all_is_constant && element->is_constant; - if (!all_is_constant) { + if (!element->is_constant) { return; } } @@ -3608,24 +3891,24 @@ void GDScriptAnalyzer::const_fold_array(GDScriptParser::ArrayNode *p_array) { for (int i = 0; i < p_array->elements.size(); i++) { array[i] = p_array->elements[i]->reduced_value; } + if (p_is_const) { + array.set_read_only(true); + } p_array->is_constant = true; p_array->reduced_value = array; } -void GDScriptAnalyzer::const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary) { - bool all_is_constant = true; - +void GDScriptAnalyzer::const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary, bool p_is_const) { for (int i = 0; i < p_dictionary->elements.size(); i++) { const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; if (element.value->type == GDScriptParser::Node::ARRAY) { - const_fold_array(static_cast<GDScriptParser::ArrayNode *>(element.value)); + const_fold_array(static_cast<GDScriptParser::ArrayNode *>(element.value), p_is_const); } else if (element.value->type == GDScriptParser::Node::DICTIONARY) { - const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(element.value)); + const_fold_dictionary(static_cast<GDScriptParser::DictionaryNode *>(element.value), p_is_const); } - all_is_constant = all_is_constant && element.key->is_constant && element.value->is_constant; - if (!all_is_constant) { + if (!element.key->is_constant || !element.value->is_constant) { return; } } @@ -3635,6 +3918,9 @@ void GDScriptAnalyzer::const_fold_dictionary(GDScriptParser::DictionaryNode *p_d const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; dict[element.key->reduced_value] = element.value->reduced_value; } + if (p_is_const) { + dict.set_read_only(true); + } p_dictionary->is_constant = true; p_dictionary->reduced_value = dict; } @@ -3647,6 +3933,9 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; // Constant has explicit type. if (p_value.get_type() == Variant::OBJECT) { + // Object is treated as a native type, not a builtin type. + result.kind = GDScriptParser::DataType::NATIVE; + Object *obj = p_value; if (!obj) { return GDScriptParser::DataType(); @@ -3661,50 +3950,43 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va scr = obj->get_script(); } if (scr.is_valid()) { - if (scr->is_valid()) { - result.script_type = scr; - result.script_path = scr->get_path(); - Ref<GDScript> gds = scr; - if (gds.is_valid()) { - result.kind = GDScriptParser::DataType::CLASS; - // This might be an inner class, so we want to get the parser for the root. - // But still get the inner class from that tree. - GDScript *current = gds.ptr(); - List<StringName> class_chain; - while (current->_owner) { - // Push to front so it's in reverse. - class_chain.push_front(current->name); - current = current->_owner; - } - - 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 StringName &E : class_chain) { - found = found->get_member(E).m_class; + Ref<GDScript> gds = scr; + if (gds.is_valid()) { + // This might be an inner class, so we want to get the parser for the root. + // But still get the inner class from that tree. + String script_path = gds->get_script_path(); + Ref<GDScriptParserRef> ref = get_parser_for(script_path); + if (ref.is_null()) { + push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); + GDScriptParser::DataType error_type; + error_type.kind = GDScriptParser::DataType::VARIANT; + return error_type; + } + Error err = ref->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); + GDScriptParser::ClassNode *found = nullptr; + if (err == OK) { + found = ref->get_parser()->find_class(gds->fully_qualified_name); + if (found != nullptr) { + err = resolve_class_inheritance(found, p_source); } - - result.class_type = found; - result.script_path = ref->get_parser()->script_path; - } else { - result.kind = GDScriptParser::DataType::SCRIPT; } - result.native_type = scr->get_instance_base_type(); + if (err || found == nullptr) { + push_error(vformat(R"(Could not resolve script "%s".)", script_path), p_source); + GDScriptParser::DataType error_type; + error_type.kind = GDScriptParser::DataType::VARIANT; + return error_type; + } + + result.kind = GDScriptParser::DataType::CLASS; + result.native_type = found->get_datatype().native_type; + result.class_type = found; + result.script_path = ref->get_parser()->script_path; } else { - push_error(vformat(R"(Constant value uses script from "%s" which is loaded but not compiled.)", scr->get_path()), p_source); - result.kind = GDScriptParser::DataType::VARIANT; - result.type_source = GDScriptParser::DataType::UNDETECTED; - result.is_meta_type = false; + result.kind = GDScriptParser::DataType::SCRIPT; + result.native_type = scr->get_instance_base_type(); + result.script_path = scr->get_path(); } + result.script_type = scr; } else { result.kind = GDScriptParser::DataType::NATIVE; if (result.native_type == GDScriptNativeClass::get_class_static()) { @@ -3716,20 +3998,21 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va return result; } -GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptParser::DataType &p_meta_type) const { +GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptParser::DataType &p_meta_type) { GDScriptParser::DataType result = p_meta_type; result.is_meta_type = false; - result.is_constant = false; if (p_meta_type.kind == GDScriptParser::DataType::ENUM) { result.builtin_type = Variant::INT; + } else { + result.is_constant = false; } return result; } -GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo &p_property) const { +GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo &p_property, bool p_is_arg) const { GDScriptParser::DataType result; result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - if (p_property.type == Variant::NIL && (p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) { + if (p_property.type == Variant::NIL && (p_is_arg || (p_property.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) { // Variant result.kind = GDScriptParser::DataType::VARIANT; return result; @@ -3779,11 +4062,12 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo r_default_arg_count = 0; StringName function_name = p_function; + bool was_enum = false; if (p_base_type.kind == GDScriptParser::DataType::ENUM) { + was_enum = true; if (p_base_type.is_meta_type) { // Enum type can be treated as a dictionary value. p_base_type.kind = GDScriptParser::DataType::BUILTIN; - p_base_type.builtin_type = Variant::DICTIONARY; p_base_type.is_meta_type = false; } else { push_error("Cannot call function on enum value.", p_source); @@ -3806,6 +4090,10 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo 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); + // Cannot use non-const methods on enums. + if (!r_static && was_enum && !(E.flags & METHOD_FLAG_CONST)) { + push_error(vformat(R"*(Cannot call non-const Dictionary function "%s()" on enum "%s".)*", p_function, p_base_type.enum_type), p_source); + } return true; } } @@ -3813,6 +4101,25 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo return false; } + StringName base_native = p_base_type.native_type; + if (base_native != StringName()) { + // Empty native class might happen in some Script implementations. + // Just ignore it. + if (!class_exists(base_native)) { + push_error(vformat("Native class %s used in script doesn't exist or isn't exposed.", base_native), p_source); + return false; + } else if (p_is_constructor && !ClassDB::can_instantiate(base_native)) { + if (p_base_type.kind == GDScriptParser::DataType::CLASS) { + push_error(vformat(R"(Class "%s" cannot be constructed as it is based on abstract native class "%s".)", p_base_type.class_type->fqcn.get_file(), base_native), p_source); + } else if (p_base_type.kind == GDScriptParser::DataType::SCRIPT) { + push_error(vformat(R"(Script "%s" cannot be constructed as it is based on abstract native class "%s".)", p_base_type.script_path.get_file(), base_native), p_source); + } else { + push_error(vformat(R"(Native class "%s" cannot be constructed as it is abstract.)", base_native), p_source); + } + return false; + } + } + if (p_is_constructor) { function_name = "_init"; r_static = true; @@ -3828,8 +4135,12 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo push_error(vformat(R"(Member "%s" is not a function.)", function_name), p_source); return false; } + + resolve_class_member(base_class, function_name, p_source); found_function = base_class->get_member(function_name).function; } + + resolve_class_inheritance(base_class, p_source); base_class = base_class->base_type.class_type; } @@ -3837,11 +4148,11 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo r_static = p_is_constructor || found_function->is_static; for (int i = 0; i < found_function->parameters.size(); i++) { r_par_types.push_back(found_function->parameters[i]->get_datatype()); - if (found_function->parameters[i]->default_value != nullptr) { + if (found_function->parameters[i]->initializer != nullptr) { r_default_arg_count++; } } - r_return_type = found_function->get_datatype(); + r_return_type = p_is_constructor ? p_base_type : found_function->get_datatype(); r_return_type.is_meta_type = false; r_return_type.is_coroutine = found_function->is_coroutine; @@ -3869,17 +4180,6 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo } } - StringName base_native = p_base_type.native_type; -#ifdef DEBUG_ENABLED - if (base_native != StringName()) { - // Empty native class might happen in some Script implementations. - // Just ignore it. - if (!class_exists(base_native)) { - ERR_FAIL_V_MSG(false, vformat("Native class %s used in script doesn't exist or isn't exposed.", base_native)); - } - } -#endif - if (p_is_constructor) { // Native types always have a default constructor. r_return_type = p_base_type; @@ -3907,7 +4207,7 @@ bool GDScriptAnalyzer::function_signature_from_info(const MethodInfo &p_info, GD r_static = (p_info.flags & METHOD_FLAG_STATIC) != 0; for (const PropertyInfo &E : p_info.arguments) { - r_par_types.push_back(type_from_property(E)); + r_par_types.push_back(type_from_property(E, true)); } return true; } @@ -3916,7 +4216,7 @@ bool GDScriptAnalyzer::validate_call_arg(const MethodInfo &p_method, const GDScr List<GDScriptParser::DataType> arg_types; for (const PropertyInfo &E : p_method.arguments) { - arg_types.push_back(type_from_property(E)); + arg_types.push_back(type_from_property(E, true)); } return validate_call_arg(arg_types, p_method.default_arguments.size(), (p_method.flags & METHOD_FLAG_VARARG) != 0, p_call); @@ -3949,7 +4249,7 @@ bool GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p // Supertypes are acceptable for dynamic compliance, but it's unsafe. mark_node_unsafe(p_call); if (!is_type_compatible(arg_type, par_type)) { - push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*", p_call->function_name, i + 1, par_type.to_string(), arg_type.to_string()), p_call->arguments[i]); valid = false; @@ -3998,12 +4298,10 @@ bool GDScriptAnalyzer::is_shadowing(GDScriptParser::IdentifierNode *p_local, con base_class = base_class->base_type.class_type; } - StringName base_native = base.native_type; - - ERR_FAIL_COND_V_MSG(!class_exists(base_native), false, "Non-existent native base class."); - - StringName parent = base_native; + StringName parent = base.native_type; while (parent != StringName()) { + ERR_FAIL_COND_V_MSG(!class_exists(parent), false, "Non-existent native base class."); + 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; @@ -4036,9 +4334,6 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator } GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source) { - GDScriptParser::DataType result; - result.kind = GDScriptParser::DataType::VARIANT; - Variant::Type a_type = p_a.builtin_type; Variant::Type b_type = p_b.builtin_type; @@ -4058,28 +4353,18 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator } Variant::ValidatedOperatorEvaluator op_eval = Variant::get_validated_operator_evaluator(p_operation, a_type, b_type); - bool hard_operation = p_a.is_hard_type() && p_b.is_hard_type(); bool validated = op_eval != nullptr; - if (hard_operation && !validated) { - r_valid = false; - return result; - } else if (hard_operation && validated) { + GDScriptParser::DataType result; + if (validated) { r_valid = true; - result.type_source = GDScriptParser::DataType::ANNOTATED_INFERRED; + result.type_source = hard_operation ? GDScriptParser::DataType::ANNOTATED_INFERRED : GDScriptParser::DataType::INFERRED; result.kind = GDScriptParser::DataType::BUILTIN; result.builtin_type = Variant::get_operator_return_type(p_operation, a_type, b_type); - } else if (!hard_operation && !validated) { - r_valid = true; - result.type_source = GDScriptParser::DataType::UNDETECTED; + } else { + r_valid = !hard_operation; result.kind = GDScriptParser::DataType::VARIANT; - result.builtin_type = Variant::NIL; - } else if (!hard_operation && validated) { - r_valid = true; - result.type_source = GDScriptParser::DataType::INFERRED; - result.kind = GDScriptParser::DataType::BUILTIN; - result.builtin_type = Variant::get_operator_return_type(p_operation, a_type, b_type); } return result; @@ -4118,7 +4403,7 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ // Variant array can't be appended to typed array. valid = false; } else { - valid = is_type_compatible(p_target.get_container_element_type(), p_source.get_container_element_type(), false); + valid = is_type_compatible(p_target.get_container_element_type(), p_source.get_container_element_type(), p_allow_implicit_conversion); } } } @@ -4193,6 +4478,7 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ case GDScriptParser::DataType::VARIANT: case GDScriptParser::DataType::BUILTIN: case GDScriptParser::DataType::ENUM: + case GDScriptParser::DataType::RESOLVING: case GDScriptParser::DataType::UNRESOLVED: break; // Already solved before. } @@ -4229,6 +4515,7 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ case GDScriptParser::DataType::VARIANT: case GDScriptParser::DataType::BUILTIN: case GDScriptParser::DataType::ENUM: + case GDScriptParser::DataType::RESOLVING: case GDScriptParser::DataType::UNRESOLVED: break; // Already solved before. } @@ -4243,12 +4530,56 @@ void GDScriptAnalyzer::push_error(const String &p_message, const GDScriptParser: void GDScriptAnalyzer::mark_node_unsafe(const GDScriptParser::Node *p_node) { #ifdef DEBUG_ENABLED + if (p_node == nullptr) { + return; + } + for (int i = p_node->start_line; i <= p_node->end_line; i++) { parser->unsafe_lines.insert(i); } #endif } +void GDScriptAnalyzer::downgrade_node_type_source(GDScriptParser::Node *p_node) { + GDScriptParser::IdentifierNode *identifier = nullptr; + if (p_node->type == GDScriptParser::Node::IDENTIFIER) { + identifier = static_cast<GDScriptParser::IdentifierNode *>(p_node); + } else if (p_node->type == GDScriptParser::Node::SUBSCRIPT) { + GDScriptParser::SubscriptNode *subscript = static_cast<GDScriptParser::SubscriptNode *>(p_node); + if (subscript->is_attribute) { + identifier = subscript->attribute; + } + } + if (identifier == nullptr) { + return; + } + + GDScriptParser::Node *source = nullptr; + switch (identifier->source) { + case GDScriptParser::IdentifierNode::MEMBER_VARIABLE: { + source = identifier->variable_source; + } break; + case GDScriptParser::IdentifierNode::FUNCTION_PARAMETER: { + source = identifier->parameter_source; + } break; + case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: { + source = identifier->variable_source; + } break; + case GDScriptParser::IdentifierNode::LOCAL_ITERATOR: { + source = identifier->bind_source; + } break; + default: + break; + } + if (source == nullptr) { + return; + } + + GDScriptParser::DataType datatype; + datatype.kind = GDScriptParser::DataType::VARIANT; + source->set_datatype(datatype); +} + void GDScriptAnalyzer::mark_lambda_use_self() { for (GDScriptParser::LambdaNode *lambda : lambda_stack) { lambda->use_self = true; @@ -4275,39 +4606,51 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) { } Error GDScriptAnalyzer::resolve_inheritance() { - return resolve_inheritance(parser->head); + // Apply annotations. + for (GDScriptParser::AnnotationNode *&E : parser->head->annotations) { + resolve_annotation(E); + E->apply(parser, parser->head); + } + return resolve_class_inheritance(parser->head, true); } Error GDScriptAnalyzer::resolve_interface() { - resolve_class_interface(parser->head); + resolve_class_interface(parser->head, true); return parser->errors.is_empty() ? OK : ERR_PARSE_ERROR; } Error GDScriptAnalyzer::resolve_body() { - resolve_class_body(parser->head); + resolve_class_body(parser->head, true); return parser->errors.is_empty() ? OK : ERR_PARSE_ERROR; } -Error GDScriptAnalyzer::resolve_program() { - resolve_class_interface(parser->head); - resolve_class_body(parser->head); - +Error GDScriptAnalyzer::resolve_dependencies() { for (KeyValue<String, Ref<GDScriptParserRef>> &K : depended_parsers) { if (K.value.is_null()) { return ERR_PARSE_ERROR; } - K.value->raise_status(GDScriptParserRef::FULLY_SOLVED); + K.value->raise_status(GDScriptParserRef::INHERITANCE_SOLVED); } + return parser->errors.is_empty() ? OK : ERR_PARSE_ERROR; } Error GDScriptAnalyzer::analyze() { parser->errors.clear(); - Error err = resolve_inheritance(parser->head); + Error err = OK; + + err = resolve_inheritance(); if (err) { return err; } - return resolve_program(); + + resolve_interface(); + resolve_body(); + if (!parser->errors.is_empty()) { + return ERR_PARSE_ERROR; + } + + return resolve_dependencies(); } GDScriptAnalyzer::GDScriptAnalyzer(GDScriptParser *p_parser) { diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 217a856ce0..5397be33f0 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_analyzer.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_analyzer.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_ANALYZER_H #define GDSCRIPT_ANALYZER_H @@ -45,36 +45,41 @@ class GDScriptAnalyzer { List<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_script_class(const StringName &p_name, const GDScriptParser::ClassNode *p_current_class_node, const GDScriptParser::Node *p_member); 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); + void get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list); + + Error resolve_class_inheritance(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source = nullptr); + Error resolve_class_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive); GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type); void decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement); - // This traverses the tree to resolve all TypeNodes. - Error resolve_program(); - void resolve_annotation(GDScriptParser::AnnotationNode *p_annotation); - void resolve_class_interface(GDScriptParser::ClassNode *p_class); - void resolve_class_body(GDScriptParser::ClassNode *p_class); - void resolve_function_signature(GDScriptParser::FunctionNode *p_function); - void resolve_function_body(GDScriptParser::FunctionNode *p_function); + void resolve_class_member(GDScriptParser::ClassNode *p_class, StringName p_name, const GDScriptParser::Node *p_source = nullptr); + void resolve_class_member(GDScriptParser::ClassNode *p_class, int p_index, const GDScriptParser::Node *p_source = nullptr); + void resolve_class_interface(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source = nullptr); + void resolve_class_interface(GDScriptParser::ClassNode *p_class, bool p_recursive); + void resolve_class_body(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source = nullptr); + void resolve_class_body(GDScriptParser::ClassNode *p_class, bool p_recursive); + void resolve_function_signature(GDScriptParser::FunctionNode *p_function, const GDScriptParser::Node *p_source = nullptr, bool p_is_lambda = false); + void resolve_function_body(GDScriptParser::FunctionNode *p_function, bool p_is_lambda = false); void resolve_node(GDScriptParser::Node *p_node, bool p_is_root = true); void resolve_suite(GDScriptParser::SuiteNode *p_suite); + void resolve_assignable(GDScriptParser::AssignableNode *p_assignable, const char *p_kind); + void resolve_variable(GDScriptParser::VariableNode *p_variable, bool p_is_local); + void resolve_constant(GDScriptParser::ConstantNode *p_constant, bool p_is_local); + void resolve_parameter(GDScriptParser::ParameterNode *p_parameter); void resolve_if(GDScriptParser::IfNode *p_if); void resolve_for(GDScriptParser::ForNode *p_for); void resolve_while(GDScriptParser::WhileNode *p_while); - void resolve_variable(GDScriptParser::VariableNode *p_variable); - void resolve_constant(GDScriptParser::ConstantNode *p_constant); void resolve_assert(GDScriptParser::AssertNode *p_assert); void resolve_match(GDScriptParser::MatchNode *p_match); void resolve_match_branch(GDScriptParser::MatchBranchNode *p_match_branch, GDScriptParser::ExpressionNode *p_match_test); void resolve_match_pattern(GDScriptParser::PatternNode *p_match_pattern, GDScriptParser::ExpressionNode *p_match_test); - void resolve_parameter(GDScriptParser::ParameterNode *p_parameter); void resolve_return(GDScriptParser::ReturnNode *p_return); // Reduction functions. @@ -94,16 +99,16 @@ class GDScriptAnalyzer { void reduce_preload(GDScriptParser::PreloadNode *p_preload); void reduce_self(GDScriptParser::SelfNode *p_self); void reduce_subscript(GDScriptParser::SubscriptNode *p_subscript); - void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op); + void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op, bool p_is_root = false); void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op); - void const_fold_array(GDScriptParser::ArrayNode *p_array); - void const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary); + void const_fold_array(GDScriptParser::ArrayNode *p_array, bool p_is_const); + void const_fold_dictionary(GDScriptParser::DictionaryNode *p_dictionary, bool p_is_const); // Helpers. GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source); - GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type) const; - GDScriptParser::DataType type_from_property(const PropertyInfo &p_property) const; + static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type); + GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false) const; GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source); bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg); 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); @@ -113,11 +118,13 @@ class GDScriptAnalyzer { GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source); void update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal); bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr); - void push_error(const String &p_message, const GDScriptParser::Node *p_origin); + void push_error(const String &p_message, const GDScriptParser::Node *p_origin = nullptr); void mark_node_unsafe(const GDScriptParser::Node *p_node); + void downgrade_node_type_source(GDScriptParser::Node *p_node); void mark_lambda_use_self(); bool class_exists(const StringName &p_class) const; Ref<GDScriptParserRef> get_parser_for(const String &p_path); + static void reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype); #ifdef DEBUG_ENABLED bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context); #endif @@ -126,6 +133,7 @@ public: Error resolve_inheritance(); Error resolve_interface(); Error resolve_body(); + Error resolve_dependencies(); Error analyze(); GDScriptAnalyzer(GDScriptParser *p_parser); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index fa158591fd..e19dda090e 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_byte_codegen.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_byte_codegen.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_byte_codegen.h" @@ -164,7 +164,7 @@ void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName function->name = p_function_name; function->_script = p_script; - function->source = p_script->get_path(); + function->source = p_script->get_script_path(); #ifdef DEBUG_ENABLED function->func_cname = (String(function->source) + " - " + String(p_function_name)).utf8(); @@ -183,7 +183,7 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { ERR_PRINT("Non-zero temporary variables at end of function: " + itos(used_temporaries.size())); } #endif - append(GDScriptFunction::OPCODE_END, 0); + append_opcode(GDScriptFunction::OPCODE_END); for (int i = 0; i < temporaries.size(); i++) { int stack_index = i + max_locals + RESERVED_STACK; @@ -401,6 +401,16 @@ GDScriptFunction *GDScriptByteCodeGenerator::write_end() { function->_instruction_args_size = instr_args_max; function->_ptrcall_args_size = ptrcall_max; +#ifdef DEBUG_ENABLED + function->operator_names = operator_names; + function->setter_names = setter_names; + function->getter_names = getter_names; + function->builtin_methods_names = builtin_methods_names; + function->constructors_names = constructors_names; + function->utilities_names = utilities_names; + function->gds_utilities_names = gds_utilities_names; +#endif + ended = true; return function; } @@ -424,115 +434,115 @@ void GDScriptByteCodeGenerator::set_initial_line(int p_line) { void GDScriptByteCodeGenerator::write_type_adjust(const Address &p_target, Variant::Type p_new_type) { switch (p_new_type) { case Variant::BOOL: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_BOOL, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_BOOL); break; case Variant::INT: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_INT, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_INT); break; case Variant::FLOAT: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_FLOAT, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_FLOAT); break; case Variant::STRING: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_STRING, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_STRING); break; case Variant::VECTOR2: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR2, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR2); break; case Variant::VECTOR2I: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR2I, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR2I); break; case Variant::RECT2: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_RECT2, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_RECT2); break; case Variant::RECT2I: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_RECT2I, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_RECT2I); break; case Variant::VECTOR3: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR3, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR3); break; case Variant::VECTOR3I: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR3I, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR3I); break; case Variant::TRANSFORM2D: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_TRANSFORM2D, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_TRANSFORM2D); break; case Variant::VECTOR4: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR3, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR3); break; case Variant::VECTOR4I: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR3I, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_VECTOR3I); break; case Variant::PLANE: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_PLANE, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_PLANE); break; case Variant::QUATERNION: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_QUATERNION, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_QUATERNION); break; case Variant::AABB: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_AABB, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_AABB); break; case Variant::BASIS: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_BASIS, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_BASIS); break; case Variant::TRANSFORM3D: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_TRANSFORM3D, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_TRANSFORM3D); break; case Variant::PROJECTION: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_PROJECTION, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_PROJECTION); break; case Variant::COLOR: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_COLOR, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_COLOR); break; case Variant::STRING_NAME: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_STRING_NAME, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_STRING_NAME); break; case Variant::NODE_PATH: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_NODE_PATH, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_NODE_PATH); break; case Variant::RID: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_RID, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_RID); break; case Variant::OBJECT: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_OBJECT, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_OBJECT); break; case Variant::CALLABLE: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_CALLABLE, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_CALLABLE); break; case Variant::SIGNAL: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_SIGNAL, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_SIGNAL); break; case Variant::DICTIONARY: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_DICTIONARY, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_DICTIONARY); break; case Variant::ARRAY: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_ARRAY, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_ARRAY); break; case Variant::PACKED_BYTE_ARRAY: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_BYTE_ARRAY, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_BYTE_ARRAY); break; case Variant::PACKED_INT32_ARRAY: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_INT32_ARRAY, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_INT32_ARRAY); break; case Variant::PACKED_INT64_ARRAY: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_INT64_ARRAY, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_INT64_ARRAY); break; case Variant::PACKED_FLOAT32_ARRAY: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_FLOAT32_ARRAY, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_FLOAT32_ARRAY); break; case Variant::PACKED_FLOAT64_ARRAY: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_FLOAT64_ARRAY, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_FLOAT64_ARRAY); break; case Variant::PACKED_STRING_ARRAY: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_STRING_ARRAY, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_STRING_ARRAY); break; case Variant::PACKED_VECTOR2_ARRAY: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_VECTOR2_ARRAY, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_VECTOR2_ARRAY); break; case Variant::PACKED_VECTOR3_ARRAY: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_VECTOR3_ARRAY, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_VECTOR3_ARRAY); break; case Variant::PACKED_COLOR_ARRAY: - append(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_COLOR_ARRAY, 1); + append_opcode(GDScriptFunction::OPCODE_TYPE_ADJUST_PACKED_COLOR_ARRAY); break; case Variant::NIL: case Variant::VARIANT_MAX: @@ -546,16 +556,19 @@ void GDScriptByteCodeGenerator::write_unary_operator(const Address &p_target, Va // Gather specific operator. Variant::ValidatedOperatorEvaluator op_func = Variant::get_validated_operator_evaluator(p_operator, p_left_operand.type.builtin_type, Variant::NIL); - append(GDScriptFunction::OPCODE_OPERATOR_VALIDATED, 3); + append_opcode(GDScriptFunction::OPCODE_OPERATOR_VALIDATED); append(p_left_operand); append(Address()); append(p_target); append(op_func); +#ifdef DEBUG_ENABLED + add_debug_name(operator_names, get_operation_pos(op_func), Variant::get_operator_name(p_operator)); +#endif return; } // No specific types, perform variant evaluation. - append(GDScriptFunction::OPCODE_OPERATOR, 3); + append_opcode(GDScriptFunction::OPCODE_OPERATOR); append(p_left_operand); append(Address()); append(p_target); @@ -575,16 +588,19 @@ void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, V // 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); - append(GDScriptFunction::OPCODE_OPERATOR_VALIDATED, 3); + append_opcode(GDScriptFunction::OPCODE_OPERATOR_VALIDATED); append(p_left_operand); append(p_right_operand); append(p_target); append(op_func); +#ifdef DEBUG_ENABLED + add_debug_name(operator_names, get_operation_pos(op_func), Variant::get_operator_name(p_operator)); +#endif return; } // No specific types, perform variant evaluation. - append(GDScriptFunction::OPCODE_OPERATOR, 3); + append_opcode(GDScriptFunction::OPCODE_OPERATOR); append(p_left_operand); append(p_right_operand); append(p_target); @@ -592,28 +608,28 @@ void GDScriptByteCodeGenerator::write_binary_operator(const Address &p_target, V } void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const Address &p_source, const Address &p_type) { - append(GDScriptFunction::OPCODE_EXTENDS_TEST, 3); + append_opcode(GDScriptFunction::OPCODE_EXTENDS_TEST); append(p_source); append(p_type); append(p_target); } void GDScriptByteCodeGenerator::write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) { - append(GDScriptFunction::OPCODE_IS_BUILTIN, 2); + append_opcode(GDScriptFunction::OPCODE_IS_BUILTIN); append(p_source); append(p_target); append(p_type); } void GDScriptByteCodeGenerator::write_and_left_operand(const Address &p_left_operand) { - append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1); + append_opcode(GDScriptFunction::OPCODE_JUMP_IF_NOT); append(p_left_operand); logic_op_jump_pos1.push_back(opcodes.size()); append(0); // Jump target, will be patched. } void GDScriptByteCodeGenerator::write_and_right_operand(const Address &p_right_operand) { - append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1); + append_opcode(GDScriptFunction::OPCODE_JUMP_IF_NOT); append(p_right_operand); logic_op_jump_pos2.push_back(opcodes.size()); append(0); // Jump target, will be patched. @@ -621,29 +637,29 @@ void GDScriptByteCodeGenerator::write_and_right_operand(const Address &p_right_o void GDScriptByteCodeGenerator::write_end_and(const Address &p_target) { // If here means both operands are true. - append(GDScriptFunction::OPCODE_ASSIGN_TRUE, 1); + append_opcode(GDScriptFunction::OPCODE_ASSIGN_TRUE); append(p_target); // Jump away from the fail condition. - append(GDScriptFunction::OPCODE_JUMP, 0); + append_opcode(GDScriptFunction::OPCODE_JUMP); append(opcodes.size() + 3); // Here it means one of operands is false. patch_jump(logic_op_jump_pos1.back()->get()); patch_jump(logic_op_jump_pos2.back()->get()); logic_op_jump_pos1.pop_back(); logic_op_jump_pos2.pop_back(); - append(GDScriptFunction::OPCODE_ASSIGN_FALSE, 1); + append_opcode(GDScriptFunction::OPCODE_ASSIGN_FALSE); append(p_target); } void GDScriptByteCodeGenerator::write_or_left_operand(const Address &p_left_operand) { - append(GDScriptFunction::OPCODE_JUMP_IF, 1); + append_opcode(GDScriptFunction::OPCODE_JUMP_IF); append(p_left_operand); logic_op_jump_pos1.push_back(opcodes.size()); append(0); // Jump target, will be patched. } void GDScriptByteCodeGenerator::write_or_right_operand(const Address &p_right_operand) { - append(GDScriptFunction::OPCODE_JUMP_IF, 1); + append_opcode(GDScriptFunction::OPCODE_JUMP_IF); append(p_right_operand); logic_op_jump_pos2.push_back(opcodes.size()); append(0); // Jump target, will be patched. @@ -651,17 +667,17 @@ void GDScriptByteCodeGenerator::write_or_right_operand(const Address &p_right_op void GDScriptByteCodeGenerator::write_end_or(const Address &p_target) { // If here means both operands are false. - append(GDScriptFunction::OPCODE_ASSIGN_FALSE, 1); + append_opcode(GDScriptFunction::OPCODE_ASSIGN_FALSE); append(p_target); // Jump away from the success condition. - append(GDScriptFunction::OPCODE_JUMP, 0); + append_opcode(GDScriptFunction::OPCODE_JUMP); append(opcodes.size() + 3); // Here it means one of operands is true. patch_jump(logic_op_jump_pos1.back()->get()); patch_jump(logic_op_jump_pos2.back()->get()); logic_op_jump_pos1.pop_back(); logic_op_jump_pos2.pop_back(); - append(GDScriptFunction::OPCODE_ASSIGN_TRUE, 1); + append_opcode(GDScriptFunction::OPCODE_ASSIGN_TRUE); append(p_target); } @@ -670,18 +686,18 @@ void GDScriptByteCodeGenerator::write_start_ternary(const Address &p_target) { } void GDScriptByteCodeGenerator::write_ternary_condition(const Address &p_condition) { - append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1); + append_opcode(GDScriptFunction::OPCODE_JUMP_IF_NOT); append(p_condition); ternary_jump_fail_pos.push_back(opcodes.size()); append(0); // Jump target, will be patched. } void GDScriptByteCodeGenerator::write_ternary_true_expr(const Address &p_expr) { - append(GDScriptFunction::OPCODE_ASSIGN, 2); + append_opcode(GDScriptFunction::OPCODE_ASSIGN); append(ternary_result.back()->get()); append(p_expr); // Jump away from the false path. - append(GDScriptFunction::OPCODE_JUMP, 0); + append_opcode(GDScriptFunction::OPCODE_JUMP); ternary_jump_skip_pos.push_back(opcodes.size()); append(0); // Fail must jump here. @@ -690,7 +706,7 @@ void GDScriptByteCodeGenerator::write_ternary_true_expr(const Address &p_expr) { } void GDScriptByteCodeGenerator::write_ternary_false_expr(const Address &p_expr) { - append(GDScriptFunction::OPCODE_ASSIGN, 2); + append_opcode(GDScriptFunction::OPCODE_ASSIGN); append(ternary_result.back()->get()); append(p_expr); } @@ -707,7 +723,7 @@ void GDScriptByteCodeGenerator::write_set(const Address &p_target, const Address 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); + append_opcode(GDScriptFunction::OPCODE_SET_INDEXED_VALIDATED); append(p_target); append(p_index); append(p_source); @@ -715,7 +731,7 @@ void GDScriptByteCodeGenerator::write_set(const Address &p_target, const Address return; } else if (Variant::get_member_validated_keyed_setter(p_target.type.builtin_type)) { Variant::ValidatedKeyedSetter setter = Variant::get_member_validated_keyed_setter(p_target.type.builtin_type); - append(GDScriptFunction::OPCODE_SET_KEYED_VALIDATED, 3); + append_opcode(GDScriptFunction::OPCODE_SET_KEYED_VALIDATED); append(p_target); append(p_index); append(p_source); @@ -724,7 +740,7 @@ void GDScriptByteCodeGenerator::write_set(const Address &p_target, const Address } } - append(GDScriptFunction::OPCODE_SET_KEYED, 3); + append_opcode(GDScriptFunction::OPCODE_SET_KEYED); append(p_target); append(p_index); append(p_source); @@ -735,7 +751,7 @@ void GDScriptByteCodeGenerator::write_get(const Address &p_target, const Address if (IS_BUILTIN_TYPE(p_index, Variant::INT) && Variant::get_member_validated_indexed_getter(p_source.type.builtin_type)) { // Use indexed getter instead. Variant::ValidatedIndexedGetter getter = Variant::get_member_validated_indexed_getter(p_source.type.builtin_type); - append(GDScriptFunction::OPCODE_GET_INDEXED_VALIDATED, 3); + append_opcode(GDScriptFunction::OPCODE_GET_INDEXED_VALIDATED); append(p_source); append(p_index); append(p_target); @@ -743,7 +759,7 @@ void GDScriptByteCodeGenerator::write_get(const Address &p_target, const Address return; } else if (Variant::get_member_validated_keyed_getter(p_source.type.builtin_type)) { Variant::ValidatedKeyedGetter getter = Variant::get_member_validated_keyed_getter(p_source.type.builtin_type); - append(GDScriptFunction::OPCODE_GET_KEYED_VALIDATED, 3); + append_opcode(GDScriptFunction::OPCODE_GET_KEYED_VALIDATED); append(p_source); append(p_index); append(p_target); @@ -751,7 +767,7 @@ void GDScriptByteCodeGenerator::write_get(const Address &p_target, const Address return; } } - append(GDScriptFunction::OPCODE_GET_KEYED, 3); + append_opcode(GDScriptFunction::OPCODE_GET_KEYED); append(p_source); append(p_index); append(p_target); @@ -761,13 +777,16 @@ void GDScriptByteCodeGenerator::write_set_named(const Address &p_target, const S 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_opcode(GDScriptFunction::OPCODE_SET_NAMED_VALIDATED); append(p_target); append(p_source); append(setter); +#ifdef DEBUG_ENABLED + add_debug_name(setter_names, get_setter_pos(setter), p_name); +#endif return; } - append(GDScriptFunction::OPCODE_SET_NAMED, 2); + append_opcode(GDScriptFunction::OPCODE_SET_NAMED); append(p_target); append(p_source); append(p_name); @@ -776,26 +795,29 @@ void GDScriptByteCodeGenerator::write_set_named(const Address &p_target, const S void GDScriptByteCodeGenerator::write_get_named(const Address &p_target, const StringName &p_name, const Address &p_source) { if (HAS_BUILTIN_TYPE(p_source) && Variant::get_member_validated_getter(p_source.type.builtin_type, p_name)) { Variant::ValidatedGetter getter = Variant::get_member_validated_getter(p_source.type.builtin_type, p_name); - append(GDScriptFunction::OPCODE_GET_NAMED_VALIDATED, 2); + append_opcode(GDScriptFunction::OPCODE_GET_NAMED_VALIDATED); append(p_source); append(p_target); append(getter); +#ifdef DEBUG_ENABLED + add_debug_name(getter_names, get_getter_pos(getter), p_name); +#endif return; } - append(GDScriptFunction::OPCODE_GET_NAMED, 2); + append_opcode(GDScriptFunction::OPCODE_GET_NAMED); append(p_source); append(p_target); append(p_name); } void GDScriptByteCodeGenerator::write_set_member(const Address &p_value, const StringName &p_name) { - append(GDScriptFunction::OPCODE_SET_MEMBER, 1); + append_opcode(GDScriptFunction::OPCODE_SET_MEMBER); append(p_value); append(p_name); } void GDScriptByteCodeGenerator::write_get_member(const Address &p_target, const StringName &p_name) { - append(GDScriptFunction::OPCODE_GET_MEMBER, 1); + append_opcode(GDScriptFunction::OPCODE_GET_MEMBER); append(p_target); append(p_name); } @@ -804,11 +826,11 @@ void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_ta 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_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY); append(p_target); append(p_source); } else { - append(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN, 2); + append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); append(p_target); append(p_source); append(p_target.type.builtin_type); @@ -818,7 +840,7 @@ void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_ta 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_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_NATIVE); append(p_target); append(p_source); append(class_idx); @@ -828,7 +850,7 @@ void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_ta 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_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_SCRIPT); append(p_target); append(p_source); append(idx); @@ -837,7 +859,7 @@ void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_ta ERR_PRINT("Compiler bug: unresolved assign."); // Shouldn't get here, but fail-safe to a regular assignment - append(GDScriptFunction::OPCODE_ASSIGN, 2); + append_opcode(GDScriptFunction::OPCODE_ASSIGN); append(p_target); append(p_source); } @@ -846,45 +868,49 @@ void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_ta 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_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_ARRAY); 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_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); append(p_target); append(p_source); append(p_target.type.builtin_type); } else { - append(GDScriptFunction::OPCODE_ASSIGN, 2); + append_opcode(GDScriptFunction::OPCODE_ASSIGN); append(p_target); append(p_source); } } void GDScriptByteCodeGenerator::write_assign_true(const Address &p_target) { - append(GDScriptFunction::OPCODE_ASSIGN_TRUE, 1); + append_opcode(GDScriptFunction::OPCODE_ASSIGN_TRUE); append(p_target); } void GDScriptByteCodeGenerator::write_assign_false(const Address &p_target) { - append(GDScriptFunction::OPCODE_ASSIGN_FALSE, 1); + append_opcode(GDScriptFunction::OPCODE_ASSIGN_FALSE); append(p_target); } -void GDScriptByteCodeGenerator::write_assign_default_parameter(const Address &p_dst, const Address &p_src) { - write_assign(p_dst, p_src); +void GDScriptByteCodeGenerator::write_assign_default_parameter(const Address &p_dst, const Address &p_src, bool p_use_conversion) { + if (p_use_conversion) { + write_assign_with_conversion(p_dst, p_src); + } else { + write_assign(p_dst, p_src); + } 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_opcode(GDScriptFunction::OPCODE_STORE_GLOBAL); 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_opcode(GDScriptFunction::OPCODE_STORE_NAMED_GLOBAL); append(p_dst); append(p_global); } @@ -894,20 +920,20 @@ void GDScriptByteCodeGenerator::write_cast(const Address &p_target, const Addres switch (p_type.kind) { case GDScriptDataType::BUILTIN: { - append(GDScriptFunction::OPCODE_CAST_TO_BUILTIN, 2); + append_opcode(GDScriptFunction::OPCODE_CAST_TO_BUILTIN); index = p_type.builtin_type; } break; case GDScriptDataType::NATIVE: { int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[p_type.native_type]; Variant nc = GDScriptLanguage::get_singleton()->get_global_array()[class_idx]; - append(GDScriptFunction::OPCODE_CAST_TO_NATIVE, 3); + append_opcode(GDScriptFunction::OPCODE_CAST_TO_NATIVE); index = get_constant_pos(nc) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); } break; case GDScriptDataType::SCRIPT: case GDScriptDataType::GDSCRIPT: { Variant script = p_type.script_type; int idx = get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); - append(GDScriptFunction::OPCODE_CAST_TO_SCRIPT, 3); + append_opcode(GDScriptFunction::OPCODE_CAST_TO_SCRIPT); index = idx; } break; default: { @@ -920,46 +946,66 @@ void GDScriptByteCodeGenerator::write_cast(const Address &p_target, const Addres append(index); } +GDScriptCodeGenerator::Address GDScriptByteCodeGenerator::get_call_target(const GDScriptCodeGenerator::Address &p_target, Variant::Type p_type) { + if (p_target.mode == Address::NIL) { + GDScriptDataType type; + if (p_type != Variant::NIL) { + type.has_type = true; + type.kind = GDScriptDataType::BUILTIN; + type.builtin_type = p_type; + } + uint32_t addr = add_temporary(type); + pop_temporary(); + return Address(Address::TEMPORARY, addr, type); + } else { + return p_target; + } +} + void GDScriptByteCodeGenerator::write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) { - append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN, 2 + p_arguments.size()); + append_opcode_and_argcount(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN, 2 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } append(p_base); - append(p_target); + append(get_call_target(p_target)); append(p_arguments.size()); append(p_function_name); } void GDScriptByteCodeGenerator::write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) { - append(GDScriptFunction::OPCODE_CALL_SELF_BASE, 1 + p_arguments.size()); + append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_SELF_BASE, 1 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(p_target); + append(get_call_target(p_target)); append(p_arguments.size()); append(p_function_name); } void GDScriptByteCodeGenerator::write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) { - append(GDScriptFunction::OPCODE_CALL_ASYNC, 2 + p_arguments.size()); + append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_ASYNC, 2 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } append(p_base); - append(p_target); + append(get_call_target(p_target)); append(p_arguments.size()); append(p_function_name); } -void GDScriptByteCodeGenerator::write_call_gdscript_utility(const Address &p_target, GDScriptUtilityFunctions::FunctionPtr p_function, const Vector<Address> &p_arguments) { - append(GDScriptFunction::OPCODE_CALL_GDSCRIPT_UTILITY, 1 + p_arguments.size()); +void GDScriptByteCodeGenerator::write_call_gdscript_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) { + append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_GDSCRIPT_UTILITY, 1 + p_arguments.size()); + GDScriptUtilityFunctions::FunctionPtr gds_function = GDScriptUtilityFunctions::get_function(p_function); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(p_target); + append(get_call_target(p_target)); append(p_arguments.size()); - append(p_function); + append(gds_function); +#ifdef DEBUG_ENABLED + add_debug_name(gds_utilities_names, get_gds_utility_pos(gds_function), p_function); +#endif } void GDScriptByteCodeGenerator::write_call_utility(const Address &p_target, const StringName &p_function, const Vector<Address> &p_arguments) { @@ -979,25 +1025,34 @@ void GDScriptByteCodeGenerator::write_call_utility(const Address &p_target, cons } if (is_validated) { - append(GDScriptFunction::OPCODE_CALL_UTILITY_VALIDATED, 1 + p_arguments.size()); + Variant::Type result_type = Variant::has_utility_function_return_value(p_function) ? Variant::get_utility_function_return_type(p_function) : Variant::NIL; + Address target = get_call_target(p_target, result_type); + Variant::Type temp_type = temporaries[target.address].type; + if (result_type != temp_type) { + write_type_adjust(target, result_type); + } + append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_UTILITY_VALIDATED, 1 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(p_target); + append(target); append(p_arguments.size()); append(Variant::get_validated_utility_function(p_function)); +#ifdef DEBUG_ENABLED + add_debug_name(utilities_names, get_utility_pos(Variant::get_validated_utility_function(p_function)), p_function); +#endif } else { - append(GDScriptFunction::OPCODE_CALL_UTILITY, 1 + p_arguments.size()); + append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_UTILITY, 1 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(p_target); + append(get_call_target(p_target)); append(p_arguments.size()); append(p_function); } } -void GDScriptByteCodeGenerator::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) { +void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, bool p_is_static, const Vector<Address> &p_arguments) { bool is_validated = false; // Check if all types are correct. @@ -1017,77 +1072,48 @@ void GDScriptByteCodeGenerator::write_call_builtin_type(const Address &p_target, if (!is_validated) { // Perform regular call. - write_call(p_target, p_base, p_method, p_arguments); + if (p_is_static) { + append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_BUILTIN_STATIC, p_arguments.size() + 1); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + append(get_call_target(p_target)); + append(p_type); + append(p_method); + append(p_arguments.size()); + } else { + write_call(p_target, p_base, p_method, p_arguments); + } 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); - } + Variant::Type result_type = Variant::get_builtin_method_return_type(p_type, p_method); + Address target = get_call_target(p_target, result_type); + Variant::Type temp_type = temporaries[target.address].type; + if (result_type != temp_type) { + write_type_adjust(target, result_type); } - append(GDScriptFunction::OPCODE_CALL_BUILTIN_TYPE_VALIDATED, 2 + p_arguments.size()); + append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_BUILTIN_TYPE_VALIDATED, 2 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } append(p_base); - append(p_target); + append(target); append(p_arguments.size()); append(Variant::get_validated_builtin_method(p_type, p_method)); +#ifdef DEBUG_ENABLED + add_debug_name(builtin_methods_names, get_builtin_method_pos(Variant::get_validated_builtin_method(p_type, p_method)), p_method); +#endif } -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()); +void GDScriptByteCodeGenerator::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) { + write_call_builtin_type(p_target, p_base, p_type, p_method, false, p_arguments); +} - 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_builtin_type_static(const Address &p_target, Variant::Type p_type, const StringName &p_method, const Vector<Address> &p_arguments) { + write_call_builtin_type(p_target, Address(), p_type, p_method, true, p_arguments); } void GDScriptByteCodeGenerator::write_call_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) { @@ -1097,11 +1123,11 @@ void GDScriptByteCodeGenerator::write_call_native_static(const Address &p_target if (!is_validated) { // Perform regular call. - append(GDScriptFunction::OPCODE_CALL_NATIVE_STATIC, p_arguments.size() + 1); + append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_NATIVE_STATIC, p_arguments.size() + 1); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(p_target); + append(get_call_target(p_target)); append(method); append(p_arguments.size()); return; @@ -1109,20 +1135,20 @@ void GDScriptByteCodeGenerator::write_call_native_static(const Address &p_target } 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()); + append_opcode_and_argcount(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++) { append(p_arguments[i]); } append(p_base); - append(p_target); + append(get_call_target(p_target)); append(p_arguments.size()); append(p_method); } void GDScriptByteCodeGenerator::write_call_ptrcall(const Address &p_target, const Address &p_base, MethodBind *p_method, const Vector<Address> &p_arguments) { -#define CASE_TYPE(m_type) \ - case Variant::m_type: \ - append(GDScriptFunction::OPCODE_CALL_PTRCALL_##m_type, 2 + p_arguments.size()); \ +#define CASE_TYPE(m_type) \ + case Variant::m_type: \ + append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_PTRCALL_##m_type, 2 + p_arguments.size()); \ break bool is_ptrcall = true; @@ -1166,19 +1192,19 @@ void GDScriptByteCodeGenerator::write_call_ptrcall(const Address &p_target, cons CASE_TYPE(PACKED_VECTOR3_ARRAY); CASE_TYPE(PACKED_COLOR_ARRAY); default: - append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL_METHOD_BIND : GDScriptFunction::OPCODE_CALL_METHOD_BIND_RET, 2 + p_arguments.size()); + append_opcode_and_argcount(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL_METHOD_BIND : GDScriptFunction::OPCODE_CALL_METHOD_BIND_RET, 2 + p_arguments.size()); is_ptrcall = false; break; } } else { - append(GDScriptFunction::OPCODE_CALL_PTRCALL_NO_RETURN, 2 + p_arguments.size()); + append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_PTRCALL_NO_RETURN, 2 + p_arguments.size()); } for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } append(p_base); - append(p_target); + append(get_call_target(p_target)); append(p_arguments.size()); append(p_method); if (is_ptrcall) { @@ -1189,45 +1215,45 @@ void GDScriptByteCodeGenerator::write_call_ptrcall(const Address &p_target, cons } void GDScriptByteCodeGenerator::write_call_self(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) { - append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN, 2 + p_arguments.size()); + append_opcode_and_argcount(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN, 2 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } append(GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); - append(p_target); + append(get_call_target(p_target)); append(p_arguments.size()); append(p_function_name); } void GDScriptByteCodeGenerator::write_call_self_async(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) { - append(GDScriptFunction::OPCODE_CALL_ASYNC, 2 + p_arguments.size()); + append_opcode_and_argcount(GDScriptFunction::OPCODE_CALL_ASYNC, 2 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } append(GDScriptFunction::ADDR_SELF); - append(p_target); + append(get_call_target(p_target)); append(p_arguments.size()); append(p_function_name); } void GDScriptByteCodeGenerator::write_call_script_function(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) { - append(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN, 2 + p_arguments.size()); + append_opcode_and_argcount(p_target.mode == Address::NIL ? GDScriptFunction::OPCODE_CALL : GDScriptFunction::OPCODE_CALL_RETURN, 2 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } append(p_base); - append(p_target); + append(get_call_target(p_target)); append(p_arguments.size()); append(p_function_name); } void GDScriptByteCodeGenerator::write_lambda(const Address &p_target, GDScriptFunction *p_function, const Vector<Address> &p_captures, bool p_use_self) { - append(p_use_self ? GDScriptFunction::OPCODE_CREATE_SELF_LAMBDA : GDScriptFunction::OPCODE_CREATE_LAMBDA, 1 + p_captures.size()); + append_opcode_and_argcount(p_use_self ? GDScriptFunction::OPCODE_CREATE_SELF_LAMBDA : GDScriptFunction::OPCODE_CREATE_LAMBDA, 1 + p_captures.size()); for (int i = 0; i < p_captures.size(); i++) { append(p_captures[i]); } - append(p_target); + append(get_call_target(p_target)); append(p_captures.size()); append(p_function); } @@ -1262,41 +1288,44 @@ void GDScriptByteCodeGenerator::write_construct(const Address &p_target, Variant } } if (valid_constructor >= 0) { - append(GDScriptFunction::OPCODE_CONSTRUCT_VALIDATED, 1 + p_arguments.size()); + append_opcode_and_argcount(GDScriptFunction::OPCODE_CONSTRUCT_VALIDATED, 1 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(p_target); + append(get_call_target(p_target)); append(p_arguments.size()); append(Variant::get_validated_constructor(p_type, valid_constructor)); +#ifdef DEBUG_ENABLED + add_debug_name(constructors_names, get_constructor_pos(Variant::get_validated_constructor(p_type, valid_constructor)), Variant::get_type_name(p_type)); +#endif return; } } - append(GDScriptFunction::OPCODE_CONSTRUCT, 1 + p_arguments.size()); + append_opcode_and_argcount(GDScriptFunction::OPCODE_CONSTRUCT, 1 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(p_target); + append(get_call_target(p_target)); append(p_arguments.size()); append(p_type); } void GDScriptByteCodeGenerator::write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) { - append(GDScriptFunction::OPCODE_CONSTRUCT_ARRAY, 1 + p_arguments.size()); + append_opcode_and_argcount(GDScriptFunction::OPCODE_CONSTRUCT_ARRAY, 1 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(p_target); + append(get_call_target(p_target)); append(p_arguments.size()); } void GDScriptByteCodeGenerator::write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) { - append(GDScriptFunction::OPCODE_CONSTRUCT_TYPED_ARRAY, 2 + p_arguments.size()); + append_opcode_and_argcount(GDScriptFunction::OPCODE_CONSTRUCT_TYPED_ARRAY, 2 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(p_target); + append(get_call_target(p_target)); if (p_element_type.script_type) { Variant script_type = Ref<Script>(p_element_type.script_type); int addr = get_constant_pos(script_type); @@ -1311,30 +1340,30 @@ void GDScriptByteCodeGenerator::write_construct_typed_array(const Address &p_tar } void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) { - append(GDScriptFunction::OPCODE_CONSTRUCT_DICTIONARY, 1 + p_arguments.size()); + append_opcode_and_argcount(GDScriptFunction::OPCODE_CONSTRUCT_DICTIONARY, 1 + p_arguments.size()); for (int i = 0; i < p_arguments.size(); i++) { append(p_arguments[i]); } - append(p_target); + append(get_call_target(p_target)); append(p_arguments.size() / 2); // This is number of key-value pairs, so only half of actual arguments. } void GDScriptByteCodeGenerator::write_await(const Address &p_target, const Address &p_operand) { - append(GDScriptFunction::OPCODE_AWAIT, 1); + append_opcode(GDScriptFunction::OPCODE_AWAIT); append(p_operand); - append(GDScriptFunction::OPCODE_AWAIT_RESUME, 1); + append_opcode(GDScriptFunction::OPCODE_AWAIT_RESUME); append(p_target); } void GDScriptByteCodeGenerator::write_if(const Address &p_condition) { - append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1); + append_opcode(GDScriptFunction::OPCODE_JUMP_IF_NOT); append(p_condition); if_jmp_addrs.push_back(opcodes.size()); append(0); // Jump destination, will be patched. } void GDScriptByteCodeGenerator::write_else() { - append(GDScriptFunction::OPCODE_JUMP, 0); // Jump from true if block; + append_opcode(GDScriptFunction::OPCODE_JUMP); // Jump from true if block; int else_jmp_addr = opcodes.size(); append(0); // Jump destination, will be patched. @@ -1349,7 +1378,7 @@ void GDScriptByteCodeGenerator::write_endif() { } void GDScriptByteCodeGenerator::write_jump_if_shared(const Address &p_value) { - append(GDScriptFunction::OPCODE_JUMP_IF_SHARED, 1); + append_opcode(GDScriptFunction::OPCODE_JUMP_IF_SHARED); append(p_value); if_jmp_addrs.push_back(opcodes.size()); append(0); // Jump destination, will be patched. @@ -1373,7 +1402,7 @@ void GDScriptByteCodeGenerator::write_for_assignment(const Address &p_variable, const Address &container = for_container_variables.back()->get(); // Assign container. - append(GDScriptFunction::OPCODE_ASSIGN, 2); + append_opcode(GDScriptFunction::OPCODE_ASSIGN); append(container); append(p_list); @@ -1475,19 +1504,19 @@ void GDScriptByteCodeGenerator::write_for() { } // Begin loop. - append(begin_opcode, 3); + append_opcode(begin_opcode); append(counter); append(container); append(iterator); for_jmp_addrs.push_back(opcodes.size()); append(0); // End of loop address, will be patched. - append(GDScriptFunction::OPCODE_JUMP, 0); + append_opcode(GDScriptFunction::OPCODE_JUMP); append(opcodes.size() + 6); // Skip over 'continue' code. // Next iteration. int continue_addr = opcodes.size(); continue_addrs.push_back(continue_addr); - append(iterate_opcode, 3); + append_opcode(iterate_opcode); append(counter); append(container); append(iterator); @@ -1497,7 +1526,7 @@ void GDScriptByteCodeGenerator::write_for() { void GDScriptByteCodeGenerator::write_endfor() { // Jump back to loop check. - append(GDScriptFunction::OPCODE_JUMP, 0); + append_opcode(GDScriptFunction::OPCODE_JUMP); append(continue_addrs.back()->get()); continue_addrs.pop_back(); @@ -1526,7 +1555,7 @@ void GDScriptByteCodeGenerator::start_while_condition() { void GDScriptByteCodeGenerator::write_while(const Address &p_condition) { // Condition check. - append(GDScriptFunction::OPCODE_JUMP_IF_NOT, 1); + append_opcode(GDScriptFunction::OPCODE_JUMP_IF_NOT); append(p_condition); while_jmp_addrs.push_back(opcodes.size()); append(0); // End of loop address, will be patched. @@ -1534,7 +1563,7 @@ void GDScriptByteCodeGenerator::write_while(const Address &p_condition) { void GDScriptByteCodeGenerator::write_endwhile() { // Jump back to loop check. - append(GDScriptFunction::OPCODE_JUMP, 0); + append_opcode(GDScriptFunction::OPCODE_JUMP); append(continue_addrs.back()->get()); continue_addrs.pop_back(); @@ -1549,51 +1578,23 @@ void GDScriptByteCodeGenerator::write_endwhile() { current_breaks_to_patch.pop_back(); } -void GDScriptByteCodeGenerator::start_match() { - match_continues_to_patch.push_back(List<int>()); -} - -void GDScriptByteCodeGenerator::start_match_branch() { - // Patch continue statements. - 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. - match_continues_to_patch.push_back(List<int>()); -} - -void GDScriptByteCodeGenerator::end_match() { - // Patch continue statements. - for (const int &E : match_continues_to_patch.back()->get()) { - patch_jump(E); - } - match_continues_to_patch.pop_back(); -} - void GDScriptByteCodeGenerator::write_break() { - append(GDScriptFunction::OPCODE_JUMP, 0); + append_opcode(GDScriptFunction::OPCODE_JUMP); current_breaks_to_patch.back()->get().push_back(opcodes.size()); append(0); } void GDScriptByteCodeGenerator::write_continue() { - append(GDScriptFunction::OPCODE_JUMP, 0); + append_opcode(GDScriptFunction::OPCODE_JUMP); append(continue_addrs.back()->get()); } -void GDScriptByteCodeGenerator::write_continue_match() { - append(GDScriptFunction::OPCODE_JUMP, 0); - match_continues_to_patch.back()->get().push_back(opcodes.size()); - append(0); -} - void GDScriptByteCodeGenerator::write_breakpoint() { - append(GDScriptFunction::OPCODE_BREAKPOINT, 0); + append_opcode(GDScriptFunction::OPCODE_BREAKPOINT); } void GDScriptByteCodeGenerator::write_newline(int p_line) { - append(GDScriptFunction::OPCODE_LINE, 0); + append_opcode(GDScriptFunction::OPCODE_LINE); append(p_line); current_line = p_line; } @@ -1611,23 +1612,23 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) { Variant script = element_type.script_type; int script_idx = get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); - append(GDScriptFunction::OPCODE_RETURN_TYPED_ARRAY, 2); + append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_ARRAY); append(p_return_value); append(script_idx); append(element_type.kind == GDScriptDataType::BUILTIN ? element_type.builtin_type : Variant::OBJECT); append(element_type.native_type); } else if (function->return_type.kind == GDScriptDataType::BUILTIN && p_return_value.type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type != p_return_value.type.builtin_type) { // Add conversion. - append(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN, 1); + append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN); append(p_return_value); append(function->return_type.builtin_type); } else { // Just assign. - append(GDScriptFunction::OPCODE_RETURN, 1); + append_opcode(GDScriptFunction::OPCODE_RETURN); append(p_return_value); } } else { - append(GDScriptFunction::OPCODE_RETURN, 1); + append_opcode(GDScriptFunction::OPCODE_RETURN); append(p_return_value); } } else { @@ -1640,19 +1641,19 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) { int script_idx = get_constant_pos(script); script_idx |= (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); - append(GDScriptFunction::OPCODE_RETURN_TYPED_ARRAY, 2); + append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_ARRAY); append(p_return_value); append(script_idx); append(element_type.kind == GDScriptDataType::BUILTIN ? element_type.builtin_type : Variant::OBJECT); append(element_type.native_type); } else { - append(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN, 1); + append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN); append(p_return_value); append(function->return_type.builtin_type); } } break; case GDScriptDataType::NATIVE: { - append(GDScriptFunction::OPCODE_RETURN_TYPED_NATIVE, 2); + append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_NATIVE); append(p_return_value); int class_idx = GDScriptLanguage::get_singleton()->get_global_map()[function->return_type.native_type]; Variant nc = GDScriptLanguage::get_singleton()->get_global_array()[class_idx]; @@ -1664,7 +1665,7 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) { Variant script = function->return_type.script_type; int script_idx = get_constant_pos(script) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); - append(GDScriptFunction::OPCODE_RETURN_TYPED_SCRIPT, 2); + append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_SCRIPT); append(p_return_value); append(script_idx); } break; @@ -1672,7 +1673,7 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) { ERR_PRINT("Compiler bug: unresolved return."); // Shouldn't get here, but fail-safe to a regular return; - append(GDScriptFunction::OPCODE_RETURN, 1); + append_opcode(GDScriptFunction::OPCODE_RETURN); append(p_return_value); } break; } @@ -1680,7 +1681,7 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) { } void GDScriptByteCodeGenerator::write_assert(const Address &p_test, const Address &p_message) { - append(GDScriptFunction::OPCODE_ASSERT, 2); + append_opcode(GDScriptFunction::OPCODE_ASSERT); append(p_test); append(p_message); } diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 7dd51845df..8aa02b86c4 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_byte_codegen.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_byte_codegen.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_BYTE_CODEGEN_H #define GDSCRIPT_BYTE_CODEGEN_H @@ -95,6 +95,24 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { RBMap<MethodBind *, int> method_bind_map; RBMap<GDScriptFunction *, int> lambdas_map; +#if DEBUG_ENABLED + // Keep method and property names for pointer and validated operations. + // Used when disassembling the bytecode. + Vector<String> operator_names; + Vector<String> setter_names; + Vector<String> getter_names; + Vector<String> builtin_methods_names; + Vector<String> constructors_names; + Vector<String> utilities_names; + Vector<String> gds_utilities_names; + void add_debug_name(Vector<String> &vector, int index, const String &name) { + if (index >= vector.size()) { + vector.resize(index + 1); + } + vector.write[index] = name; + } +#endif + // Lists since these can be nested. List<int> if_jmp_addrs; List<int> for_jmp_addrs; @@ -113,7 +131,6 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { List<int> ternary_jump_skip_pos; List<List<int>> current_breaks_to_patch; - List<List<int>> match_continues_to_patch; void add_stack_identifier(const StringName &p_id, int p_stackpos) { if (locals.size() > max_locals) { @@ -309,6 +326,8 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { } } + Address get_call_target(const Address &p_target, Variant::Type p_type = Variant::NIL); + int address_of(const Address &p_address) { switch (p_address.mode) { case Address::SELF: @@ -331,8 +350,13 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { return -1; // Unreachable. } - void append(GDScriptFunction::Opcode p_code, int p_argument_count) { - opcodes.push_back((p_code & GDScriptFunction::INSTR_MASK) | (p_argument_count << GDScriptFunction::INSTR_BITS)); + void append_opcode(GDScriptFunction::Opcode p_code) { + opcodes.push_back(p_code); + } + + void append_opcode_and_argcount(GDScriptFunction::Opcode p_code, int p_argument_count) { + opcodes.push_back(p_code); + opcodes.push_back(p_argument_count); instr_args_max = MAX(instr_args_max, p_argument_count); } @@ -453,7 +477,7 @@ public: 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_assign_default_parameter(const Address &p_dst, const Address &p_src, bool p_use_conversion) 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; @@ -461,7 +485,8 @@ public: virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) override; virtual void write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; 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; + void write_call_builtin_type(const Address &p_target, const Address &p_base, Variant::Type p_type, const StringName &p_method, bool p_is_static, const Vector<Address> &p_arguments); + virtual void write_call_gdscript_utility(const Address &p_target, const StringName &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_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) override; @@ -488,12 +513,8 @@ public: virtual void start_while_condition() override; virtual void write_while(const Address &p_condition) override; virtual void write_endwhile() override; - virtual void start_match() override; - virtual void start_match_branch() override; - virtual void end_match() override; virtual void write_break() override; virtual void write_continue() override; - virtual void write_continue_match() override; virtual void write_breakpoint() override; virtual void write_newline(int p_line) override; virtual void write_return(const Address &p_return_value) override; diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 271296c2f9..a009e8e0a8 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_cache.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_cache.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_cache.h" @@ -34,7 +34,9 @@ #include "core/templates/vector.h" #include "gdscript.h" #include "gdscript_analyzer.h" +#include "gdscript_compiler.h" #include "gdscript_parser.h" +#include "scene/resources/packed_scene.h" bool GDScriptParserRef::is_valid() const { return parser != nullptr; @@ -48,6 +50,13 @@ GDScriptParser *GDScriptParserRef::get_parser() const { return parser; } +GDScriptAnalyzer *GDScriptParserRef::get_analyzer() { + if (analyzer == nullptr) { + analyzer = memnew(GDScriptAnalyzer(parser)); + } + return analyzer; +} + Error GDScriptParserRef::raise_status(Status p_new_status) { ERR_FAIL_COND_V(parser == nullptr, ERR_INVALID_DATA); @@ -62,23 +71,22 @@ Error GDScriptParserRef::raise_status(Status p_new_status) { result = parser->parse(GDScriptCache::get_source_code(path), path, false); break; case PARSED: { - analyzer = memnew(GDScriptAnalyzer(parser)); status = INHERITANCE_SOLVED; - Error inheritance_result = analyzer->resolve_inheritance(); + Error inheritance_result = get_analyzer()->resolve_inheritance(); if (result == OK) { result = inheritance_result; } } break; case INHERITANCE_SOLVED: { status = INTERFACE_SOLVED; - Error interface_result = analyzer->resolve_interface(); + Error interface_result = get_analyzer()->resolve_interface(); if (result == OK) { result = interface_result; } } break; case INTERFACE_SOLVED: { status = FULLY_SOLVED; - Error body_result = analyzer->resolve_body(); + Error body_result = get_analyzer()->resolve_body(); if (result == OK) { result = body_result; } @@ -95,27 +103,96 @@ Error GDScriptParserRef::raise_status(Status p_new_status) { return result; } -GDScriptParserRef::~GDScriptParserRef() { +void GDScriptParserRef::clear() { + if (cleared) { + return; + } + cleared = true; + if (parser != nullptr) { memdelete(parser); } + if (analyzer != nullptr) { memdelete(analyzer); } - MutexLock lock(GDScriptCache::singleton->lock); +} + +GDScriptParserRef::~GDScriptParserRef() { + clear(); + + MutexLock lock(GDScriptCache::singleton->mutex); GDScriptCache::singleton->parser_map.erase(path); } GDScriptCache *GDScriptCache::singleton = nullptr; +void GDScriptCache::move_script(const String &p_from, const String &p_to) { + if (singleton == nullptr || p_from == p_to) { + return; + } + + MutexLock lock(singleton->mutex); + + if (singleton->cleared) { + return; + } + + for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) { + if (E.value.has(p_from)) { + E.value.insert(p_to); + E.value.erase(p_from); + } + } + + if (singleton->parser_map.has(p_from) && !p_from.is_empty()) { + singleton->parser_map[p_to] = singleton->parser_map[p_from]; + } + singleton->parser_map.erase(p_from); + + if (singleton->shallow_gdscript_cache.has(p_from) && !p_from.is_empty()) { + singleton->shallow_gdscript_cache[p_to] = singleton->shallow_gdscript_cache[p_from]; + } + singleton->shallow_gdscript_cache.erase(p_from); + + if (singleton->full_gdscript_cache.has(p_from) && !p_from.is_empty()) { + singleton->full_gdscript_cache[p_to] = singleton->full_gdscript_cache[p_from]; + } + singleton->full_gdscript_cache.erase(p_from); +} + void GDScriptCache::remove_script(const String &p_path) { - MutexLock lock(singleton->lock); + if (singleton == nullptr) { + return; + } + + MutexLock lock(singleton->mutex); + + if (singleton->cleared) { + return; + } + + for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) { + if (!E.value.has(p_path)) { + continue; + } + E.value.erase(p_path); + } + + GDScriptCache::clear_unreferenced_packed_scenes(); + + if (singleton->parser_map.has(p_path)) { + singleton->parser_map[p_path]->clear(); + singleton->parser_map.erase(p_path); + } + + singleton->dependencies.erase(p_path); singleton->shallow_gdscript_cache.erase(p_path); singleton->full_gdscript_cache.erase(p_path); } Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptParserRef::Status p_status, Error &r_error, const String &p_owner) { - MutexLock lock(singleton->lock); + MutexLock lock(singleton->mutex); Ref<GDScriptParserRef> ref; if (!p_owner.is_empty()) { singleton->dependencies[p_owner].insert(p_path); @@ -161,8 +238,8 @@ String GDScriptCache::get_source_code(const String &p_path) { return source; } -Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const String &p_owner) { - MutexLock lock(singleton->lock); +Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_error, const String &p_owner) { + MutexLock lock(singleton->mutex); if (!p_owner.is_empty()) { singleton->dependencies[p_owner].insert(p_path); } @@ -176,15 +253,19 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const Stri Ref<GDScript> script; script.instantiate(); script->set_path(p_path, true); - script->set_script_path(p_path); script->load_source_code(p_path); - singleton->shallow_gdscript_cache[p_path] = script.ptr(); + Ref<GDScriptParserRef> parser_ref = get_parser(p_path, GDScriptParserRef::PARSED, r_error); + if (r_error == OK) { + GDScriptCompiler::make_scripts(script.ptr(), parser_ref->get_parser()->get_tree(), true); + } + + singleton->shallow_gdscript_cache[p_path] = script; return script; } Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_error, const String &p_owner, bool p_update_from_disk) { - MutexLock lock(singleton->lock); + MutexLock lock(singleton->mutex); if (!p_owner.is_empty()) { singleton->dependencies[p_owner].insert(p_path); @@ -193,38 +274,54 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro Ref<GDScript> script; r_error = OK; if (singleton->full_gdscript_cache.has(p_path)) { - script = Ref<GDScript>(singleton->full_gdscript_cache[p_path]); + script = singleton->full_gdscript_cache[p_path]; if (!p_update_from_disk) { return script; } } if (script.is_null()) { - script = get_shallow_script(p_path); - ERR_FAIL_COND_V(script.is_null(), Ref<GDScript>()); + script = get_shallow_script(p_path, r_error); + if (r_error) { + return script; + } } - r_error = script->load_source_code(p_path); - - if (r_error) { - return script; + if (p_update_from_disk) { + r_error = script->load_source_code(p_path); } - r_error = script->reload(); if (r_error) { return script; } - singleton->full_gdscript_cache[p_path] = script.ptr(); + singleton->full_gdscript_cache[p_path] = script; singleton->shallow_gdscript_cache.erase(p_path); + script->reload(true); return script; } +Ref<GDScript> GDScriptCache::get_cached_script(const String &p_path) { + MutexLock lock(singleton->mutex); + + if (singleton->full_gdscript_cache.has(p_path)) { + return singleton->full_gdscript_cache[p_path]; + } + + if (singleton->shallow_gdscript_cache.has(p_path)) { + return singleton->shallow_gdscript_cache[p_path]; + } + + return Ref<GDScript>(); +} + Error GDScriptCache::finish_compiling(const String &p_owner) { + MutexLock lock(singleton->mutex); + // Mark this as compiled. - Ref<GDScript> script = get_shallow_script(p_owner); - singleton->full_gdscript_cache[p_owner] = script.ptr(); + Ref<GDScript> script = get_cached_script(p_owner); + singleton->full_gdscript_cache[p_owner] = script; singleton->shallow_gdscript_cache.erase(p_owner); HashSet<String> depends = singleton->dependencies[p_owner]; @@ -245,13 +342,98 @@ Error GDScriptCache::finish_compiling(const String &p_owner) { return err; } +Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_error, const String &p_owner) { + MutexLock lock(singleton->mutex); + + if (singleton->packed_scene_cache.has(p_path)) { + singleton->packed_scene_dependencies[p_path].insert(p_owner); + return singleton->packed_scene_cache[p_path]; + } + + Ref<PackedScene> scene = ResourceCache::get_ref(p_path); + if (scene.is_valid()) { + singleton->packed_scene_cache[p_path] = scene; + singleton->packed_scene_dependencies[p_path].insert(p_owner); + return scene; + } + scene.instantiate(); + + r_error = OK; + if (p_path.is_empty()) { + r_error = ERR_FILE_BAD_PATH; + return scene; + } + + scene->set_path(p_path); + singleton->packed_scene_cache[p_path] = scene; + singleton->packed_scene_dependencies[p_path].insert(p_owner); + + scene->reload_from_file(); + return scene; +} + +void GDScriptCache::clear_unreferenced_packed_scenes() { + if (singleton == nullptr) { + return; + } + + MutexLock lock(singleton->mutex); + + if (singleton->cleared) { + return; + } + + for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) { + if (E.value.size() > 0 || !ResourceLoader::is_imported(E.key)) { + continue; + } + + singleton->packed_scene_dependencies.erase(E.key); + singleton->packed_scene_cache.erase(E.key); + } +} + +void GDScriptCache::clear() { + if (singleton == nullptr) { + return; + } + + MutexLock lock(singleton->mutex); + + if (singleton->cleared) { + return; + } + singleton->cleared = true; + + RBSet<Ref<GDScriptParserRef>> parser_map_refs; + for (KeyValue<String, GDScriptParserRef *> &E : singleton->parser_map) { + parser_map_refs.insert(E.value); + } + + for (Ref<GDScriptParserRef> &E : parser_map_refs) { + if (E.is_valid()) + E->clear(); + } + + singleton->packed_scene_dependencies.clear(); + singleton->packed_scene_cache.clear(); + + parser_map_refs.clear(); + singleton->parser_map.clear(); + singleton->shallow_gdscript_cache.clear(); + singleton->full_gdscript_cache.clear(); + + singleton->packed_scene_cache.clear(); + singleton->packed_scene_dependencies.clear(); +} + GDScriptCache::GDScriptCache() { singleton = this; } GDScriptCache::~GDScriptCache() { - parser_map.clear(); - shallow_gdscript_cache.clear(); - full_gdscript_cache.clear(); + if (!cleared) { + clear(); + } singleton = nullptr; } diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index 3d111ea229..c7f40f6e82 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_cache.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_cache.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_CACHE_H #define GDSCRIPT_CACHE_H @@ -36,6 +36,7 @@ #include "core/templates/hash_map.h" #include "core/templates/hash_set.h" #include "gdscript.h" +#include "scene/resources/packed_scene.h" class GDScriptAnalyzer; class GDScriptParser; @@ -56,6 +57,7 @@ private: Status status = EMPTY; Error result = OK; String path; + bool cleared = false; friend class GDScriptCache; @@ -63,7 +65,9 @@ public: bool is_valid() const; Status get_status() const; GDScriptParser *get_parser() const; + GDScriptAnalyzer *get_analyzer(); Error raise_status(Status p_new_status); + void clear(); GDScriptParserRef() {} ~GDScriptParserRef(); @@ -72,25 +76,37 @@ public: class GDScriptCache { // String key is full path. HashMap<String, GDScriptParserRef *> parser_map; - HashMap<String, GDScript *> shallow_gdscript_cache; - HashMap<String, GDScript *> full_gdscript_cache; + HashMap<String, Ref<GDScript>> shallow_gdscript_cache; + HashMap<String, Ref<GDScript>> full_gdscript_cache; HashMap<String, HashSet<String>> dependencies; + HashMap<String, Ref<PackedScene>> packed_scene_cache; + HashMap<String, HashSet<String>> packed_scene_dependencies; friend class GDScript; friend class GDScriptParserRef; + friend class GDScriptInstance; static GDScriptCache *singleton; - Mutex lock; - static void remove_script(const String &p_path); + bool cleared = false; + + Mutex mutex; public: + static void move_script(const String &p_from, const String &p_to); + static void remove_script(const String &p_path); static Ref<GDScriptParserRef> get_parser(const String &p_path, GDScriptParserRef::Status status, Error &r_error, const String &p_owner = String()); static String get_source_code(const String &p_path); - static Ref<GDScript> get_shallow_script(const String &p_path, const String &p_owner = String()); + static Ref<GDScript> get_shallow_script(const String &p_path, Error &r_error, const String &p_owner = String()); static Ref<GDScript> get_full_script(const String &p_path, Error &r_error, const String &p_owner = String(), bool p_update_from_disk = false); + static Ref<GDScript> get_cached_script(const String &p_path); static Error finish_compiling(const String &p_owner); + static Ref<PackedScene> get_packed_scene(const String &p_path, Error &r_error, const String &p_owner = ""); + static void clear_unreferenced_packed_scenes(); + + static void clear(); + GDScriptCache(); ~GDScriptCache(); }; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index 5972481c3a..6d42d152b9 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_codegen.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_codegen.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_CODEGEN_H #define GDSCRIPT_CODEGEN_H @@ -59,7 +59,7 @@ public: type = p_type; } Address(AddressMode p_mode, uint32_t p_address, const GDScriptDataType &p_type = GDScriptDataType()) { - mode = p_mode, + mode = p_mode; address = p_address; type = p_type; } @@ -113,7 +113,7 @@ public: 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_assign_default_parameter(const Address &dst, const Address &src, bool p_use_conversion) = 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; @@ -121,7 +121,7 @@ public: virtual void write_super_call(const Address &p_target, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; virtual void write_call_async(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; 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_gdscript_utility(const Address &p_target, const StringName &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_native_static(const Address &p_target, const StringName &p_class, const StringName &p_method, const Vector<Address> &p_arguments) = 0; @@ -148,12 +148,8 @@ public: virtual void start_while_condition() = 0; // Used to allow a jump to the expression evaluation. virtual void write_while(const Address &p_condition) = 0; virtual void write_endwhile() = 0; - virtual void start_match() = 0; - virtual void start_match_branch() = 0; - virtual void end_match() = 0; virtual void write_break() = 0; virtual void write_continue() = 0; - virtual void write_continue_match() = 0; virtual void write_breakpoint() = 0; virtual void write_newline(int p_line) = 0; virtual void write_return(const Address &p_return_value) = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 7acd1cdb96..c78dd1528f 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_compiler.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_compiler.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_compiler.h" @@ -43,7 +43,7 @@ bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringN return false; } - if (codegen.parameters.has(p_name) || codegen.locals.has(p_name)) { + if (_is_local_or_parameter(codegen, p_name)) { return false; //shadowed } @@ -65,6 +65,10 @@ bool GDScriptCompiler::_is_class_member_property(GDScript *owner, const StringNa return ClassDB::has_property(nc->get_name(), p_name); } +bool GDScriptCompiler::_is_local_or_parameter(CodeGen &codegen, const StringName &p_name) { + return codegen.parameters.has(p_name) || codegen.locals.has(p_name); +} + void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::Node *p_node) { if (!error.is_empty()) { return; @@ -80,7 +84,7 @@ void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::N } } -GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) const { +GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) { if (!p_datatype.is_set() || !p_datatype.is_hard_type()) { return GDScriptDataType(); } @@ -103,74 +107,44 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } break; case GDScriptParser::DataType::SCRIPT: { result.kind = GDScriptDataType::SCRIPT; - result.script_type_ref = Ref<Script>(p_datatype.script_type); + result.builtin_type = p_datatype.builtin_type; + result.script_type_ref = p_datatype.script_type; result.script_type = result.script_type_ref.ptr(); - result.native_type = result.script_type->get_instance_base_type(); + result.native_type = p_datatype.native_type; } break; case GDScriptParser::DataType::CLASS: { - // Locate class by constructing the path to it and following that path. - GDScriptParser::ClassNode *class_type = p_datatype.class_type; - if (class_type) { - result.kind = GDScriptDataType::GDSCRIPT; - result.builtin_type = p_datatype.builtin_type; - - String class_name = class_type->fqcn.split("::")[0]; - const bool is_inner_by_path = (!main_script->path.is_empty()) && (class_name == main_script->path); - const bool is_inner_by_name = (!main_script->name.is_empty()) && (class_name == main_script->name); - if (is_inner_by_path || is_inner_by_name) { - // Local class. - List<StringName> names; - while (class_type->outer) { - names.push_back(class_type->identifier->name); - class_type = class_type->outer; - } + result.kind = GDScriptDataType::GDSCRIPT; + result.builtin_type = p_datatype.builtin_type; + result.native_type = p_datatype.native_type; - Ref<GDScript> script = Ref<GDScript>(main_script); - while (names.back()) { - if (!script->subclasses.has(names.back()->get())) { - ERR_PRINT("Parser bug: Cannot locate datatype class."); - result.has_type = false; - return GDScriptDataType(); - } - script = script->subclasses[names.back()->get()]; - names.pop_back(); - } - result.script_type = script.ptr(); - result.native_type = script->get_instance_base_type(); - } else { - // Inner class. - PackedStringArray classes = class_type->fqcn.split("::"); - if (!classes.is_empty()) { - for (GDScript *script : parsed_classes) { - // Checking of inheritance structure of inner class to find a correct script link. - if (script->name == classes[classes.size() - 1]) { - PackedStringArray classes2 = script->fully_qualified_name.split("::"); - bool valid = true; - if (classes.size() != classes2.size()) { - valid = false; - } else { - for (int i = 0; i < classes.size(); i++) { - if (classes[i] != classes2[i]) { - valid = false; - break; - } - } - } - if (!valid) { - continue; - } - result.script_type_ref = Ref<GDScript>(script); - break; - } - } - } - if (result.script_type_ref.is_null()) { - result.script_type_ref = GDScriptCache::get_shallow_script(p_datatype.script_path, main_script->path); - } + bool is_local_class = parser->has_class(p_datatype.class_type); + + Ref<GDScript> script; + if (is_local_class) { + script = Ref<GDScript>(main_script); + } else { + Error err = OK; + script = GDScriptCache::get_shallow_script(p_datatype.script_path, err, p_owner->path); + if (err) { + _set_error(vformat(R"(Could not find script "%s": %s)", p_datatype.script_path, error_names[err]), nullptr); + } + } - result.script_type = result.script_type_ref.ptr(); - result.native_type = p_datatype.native_type; + if (script.is_valid()) { + script = Ref<GDScript>(script->find_class(p_datatype.class_type->fqcn)); + } + + if (script.is_null()) { + _set_error(vformat(R"(Could not find class "%s" in "%s".)", p_datatype.class_type->fqcn, p_datatype.script_path), nullptr); + return GDScriptDataType(); + } else { + // Only hold a strong reference if the owner of the element qualified with this type is not local, to avoid cyclic references (leaks). + // TODO: Might lead to use after free if script_type is a subclass and is used after its parent is freed. + if (!is_local_class) { + result.script_type_ref = script; } + result.script_type = script.ptr(); + result.native_type = p_datatype.native_type; } } break; case GDScriptParser::DataType::ENUM: @@ -182,6 +156,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D result.builtin_type = Variant::INT; } break; + case GDScriptParser::DataType::RESOLVING: case GDScriptParser::DataType::UNRESOLVED: { ERR_PRINT("Parser bug: converting unresolved type."); return GDScriptDataType(); @@ -189,13 +164,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } if (p_datatype.has_container_element_type()) { - result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type())); - } - - // Only hold strong reference to the script if it's not the owner of the - // element qualified with this type, to avoid cyclic references (leaks). - if (result.script_type && result.script_type == p_owner) { - result.script_type_ref = Ref<Script>(); + result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type(), p_owner)); } return result; @@ -244,7 +213,7 @@ static bool _have_exact_arguments(const MethodBind *p_method, const Vector<GDScr } GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer, const GDScriptCodeGenerator::Address &p_index_addr) { - if (p_expression->is_constant) { + if (p_expression->is_constant && !(p_expression->get_datatype().is_meta_type && p_expression->get_datatype().kind == GDScriptParser::DataType::CLASS)) { return codegen.add_constant(p_expression->reduced_value); } @@ -367,7 +336,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // This is so one autoload doesn't try to load another before it's compiled. HashMap<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())); + GDScriptCodeGenerator::Address global = codegen.add_temporary(_gdtype_from_datatype(in->get_datatype(), codegen.script)); int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; gen->write_store_global(global, idx); return global; @@ -390,11 +359,22 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code if (class_node->identifier && class_node->identifier->name == identifier) { res = Ref<GDScript>(main_script); } else { - res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier)); - if (res.is_null()) { - _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); - r_error = ERR_COMPILATION_FAILED; - return GDScriptCodeGenerator::Address(); + String global_class_path = ScriptServer::get_global_class_path(identifier); + if (ResourceLoader::get_resource_type(global_class_path) == "GDScript") { + Error err = OK; + res = GDScriptCache::get_full_script(global_class_path, err); + if (err != OK) { + _set_error("Can't load global class " + String(identifier), p_expression); + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); + } + } else { + res = ResourceLoader::load(global_class_path); + if (res.is_null()) { + _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); + } } } @@ -434,7 +414,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code Vector<GDScriptCodeGenerator::Address> values; // Create the result temporary first since it's the last to be killed. - GDScriptDataType array_type = _gdtype_from_datatype(an->get_datatype()); + GDScriptDataType array_type = _gdtype_from_datatype(an->get_datatype(), codegen.script); GDScriptCodeGenerator::Address result = codegen.add_temporary(array_type); for (int i = 0; i < an->elements.size(); i++) { @@ -510,35 +490,42 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } break; case GDScriptParser::Node::CAST: { const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); - GDScriptParser::DataType og_cast_type = cn->cast_type->get_datatype(); - GDScriptDataType cast_type = _gdtype_from_datatype(og_cast_type); + GDScriptParser::DataType og_cast_type = cn->get_datatype(); + GDScriptDataType cast_type = _gdtype_from_datatype(og_cast_type, codegen.script); - if (og_cast_type.kind == GDScriptParser::DataType::ENUM) { - // Enum types are usually treated as dictionaries, but in this case we want to cast to an integer. - cast_type.kind = GDScriptDataType::BUILTIN; - cast_type.builtin_type = Variant::INT; - } + GDScriptCodeGenerator::Address result; + if (cast_type.has_type) { + if (og_cast_type.kind == GDScriptParser::DataType::ENUM) { + // Enum types are usually treated as dictionaries, but in this case we want to cast to an integer. + cast_type.kind = GDScriptDataType::BUILTIN; + cast_type.builtin_type = Variant::INT; + } - // Create temporary for result first since it will be deleted last. - GDScriptCodeGenerator::Address result = codegen.add_temporary(cast_type); + // Create temporary for result first since it will be deleted last. + result = codegen.add_temporary(cast_type); - GDScriptCodeGenerator::Address src = _parse_expression(codegen, r_error, cn->operand); + GDScriptCodeGenerator::Address src = _parse_expression(codegen, r_error, cn->operand); - gen->write_cast(result, src, cast_type); + gen->write_cast(result, src, cast_type); - if (src.mode == GDScriptCodeGenerator::Address::TEMPORARY) { - gen->pop_temporary(); + if (src.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + gen->pop_temporary(); + } + } else { + result = _parse_expression(codegen, r_error, cn->operand); } return result; } break; case GDScriptParser::Node::CALL: { 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; + GDScriptDataType type = _gdtype_from_datatype(call->get_datatype(), codegen.script); + GDScriptCodeGenerator::Address result; + if (p_root) { + result = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::NIL); + } else { + result = codegen.add_temporary(type); + } Vector<GDScriptCodeGenerator::Address> arguments; for (int i = 0; i < call->arguments.size(); i++) { @@ -559,7 +546,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code gen->write_call_utility(result, call->function_name, arguments); } else if (!call->is_super && call->callee->type == GDScriptParser::Node::IDENTIFIER && GDScriptUtilityFunctions::function_exists(call->function_name)) { // GDScript utility function. - gen->write_call_gdscript_utility(result, GDScriptUtilityFunctions::get_function(call->function_name), arguments); + gen->write_call_gdscript_utility(result, call->function_name, arguments); } else { // Regular function. const GDScriptParser::ExpressionNode *callee = call->callee; @@ -589,13 +576,13 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code if (within_await) { gen->write_call_async(result, self, call->function_name, arguments); } else { - gen->write_call(return_addr, self, call->function_name, arguments); + gen->write_call(result, self, call->function_name, arguments); } } else { if (within_await) { gen->write_call_self_async(result, call->function_name, arguments); } else { - gen->write_call_self(return_addr, call->function_name, arguments); + gen->write_call_self(result, call->function_name, arguments); } } } else if (callee->type == GDScriptParser::Node::SUBSCRIPT) { @@ -634,12 +621,12 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code gen->write_call_method_bind(result, base, method, arguments); } } else { - gen->write_call(return_addr, base, call->function_name, arguments); + gen->write_call(result, base, call->function_name, arguments); } } else if (base.type.has_type && base.type.kind == GDScriptDataType::BUILTIN) { gen->write_call_builtin_type(result, base, base.type.builtin_type, call->function_name, arguments); } else { - gen->write_call(return_addr, base, call->function_name, arguments); + gen->write_call(result, base, call->function_name, arguments); } if (base.mode == GDScriptCodeGenerator::Address::TEMPORARY) { gen->pop_temporary(); @@ -670,7 +657,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code Vector<GDScriptCodeGenerator::Address> args; args.push_back(codegen.add_constant(NodePath(get_node->full_path))); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype(), codegen.script)); MethodBind *get_node_method = ClassDB::get_method("Node", "get_node"); gen->write_call_ptrcall(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args); @@ -686,7 +673,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::AWAIT: { const GDScriptParser::AwaitNode *await = static_cast<const GDScriptParser::AwaitNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype(), codegen.script)); within_await = true; GDScriptCodeGenerator::Address argument = _parse_expression(codegen, r_error, await->to_await); within_await = false; @@ -705,7 +692,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Indexing operator. case GDScriptParser::Node::SUBSCRIPT: { const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base); if (r_error) { @@ -735,7 +722,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Remove result temp as we don't need it. gen->pop_temporary(); // Faster than indexing self (as if no self. had been used). - return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, MI->value.index, _gdtype_from_datatype(subscript->get_datatype())); + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, MI->value.index, _gdtype_from_datatype(subscript->get_datatype(), codegen.script)); } } @@ -773,7 +760,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::UNARY_OPERATOR: { const GDScriptParser::UnaryOpNode *unary = static_cast<const GDScriptParser::UnaryOpNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(unary->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(unary->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, unary->operand); if (r_error) { @@ -791,7 +778,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::BINARY_OPERATOR: { const GDScriptParser::BinaryOpNode *binary = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(binary->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(binary->get_datatype(), codegen.script)); switch (binary->operation) { case GDScriptParser::BinaryOpNode::OP_LOGIC_AND: { @@ -867,7 +854,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::TERNARY_OPERATOR: { // x IF a ELSE y operator with early out on failure. const GDScriptParser::TernaryOpNode *ternary = static_cast<const GDScriptParser::TernaryOpNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(ternary->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(ternary->get_datatype(), codegen.script)); gen->write_start_ternary(result); @@ -944,7 +931,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code StringName var_name = identifier->name; if (_is_class_member_property(codegen, var_name)) { assign_class_member_property = var_name; - } else if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) { + } else if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) { is_member_property = true; member_property_setter_function = codegen.script->member_indices[var_name].setter; member_property_has_setter = member_property_setter_function != StringName(); @@ -985,7 +972,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code break; } const GDScriptParser::SubscriptNode *subscript_elem = E->get(); - GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript_elem->get_datatype())); + GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript_elem->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address key; StringName name; @@ -1024,8 +1011,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Perform operator if any. if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { - GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype())); - GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype())); + GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype(), codegen.script)); + GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype(), codegen.script)); if (subscript->is_attribute) { gen->write_get_named(value, name, prev_base); } else { @@ -1130,8 +1117,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code StringName name = static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name; if (has_operation) { - GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype())); - GDScriptCodeGenerator::Address member = codegen.add_temporary(_gdtype_from_datatype(assignment->assignee->get_datatype())); + GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype(), codegen.script)); + GDScriptCodeGenerator::Address member = codegen.add_temporary(_gdtype_from_datatype(assignment->assignee->get_datatype(), codegen.script)); gen->write_get_member(member, name); gen->write_binary_operator(op_result, assignment->variant_op, member, assigned_value); gen->pop_temporary(); // Pop member temp. @@ -1155,7 +1142,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code bool is_in_setter = false; StringName setter_function; StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name; - if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) { + if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) { is_member = true; setter_function = codegen.script->member_indices[var_name].setter; has_setter = setter_function != StringName(); @@ -1184,7 +1171,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code bool has_operation = assignment->operation != GDScriptParser::AssignmentNode::OP_NONE; if (has_operation) { // Perform operation. - GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype())); + GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype(), codegen.script)); 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; @@ -1196,7 +1183,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code to_assign = assigned_value; } - GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype()); + GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype(), codegen.script); if (has_setter && !is_in_setter) { // Call setter. @@ -1226,7 +1213,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } break; case GDScriptParser::Node::LAMBDA: { const GDScriptParser::LambdaNode *lambda = static_cast<const GDScriptParser::LambdaNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(lambda->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(lambda->get_datatype(), codegen.script)); Vector<GDScriptCodeGenerator::Address> captures; captures.resize(lambda->captures.size()); @@ -1276,9 +1263,30 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c equality_type.kind = GDScriptDataType::BUILTIN; equality_type.builtin_type = Variant::BOOL; + GDScriptCodeGenerator::Address type_string_addr = codegen.add_constant(Variant::STRING); + GDScriptCodeGenerator::Address type_string_name_addr = codegen.add_constant(Variant::STRING_NAME); + // Check type equality. GDScriptCodeGenerator::Address type_equality_addr = codegen.add_temporary(equality_type); codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_EQUAL, p_type_addr, literal_type_addr); + + // Check if StringName <-> String comparison is possible. + GDScriptCodeGenerator::Address type_comp_addr_1 = codegen.add_temporary(equality_type); + GDScriptCodeGenerator::Address type_comp_addr_2 = codegen.add_temporary(equality_type); + + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_EQUAL, p_type_addr, type_string_addr); + codegen.generator->write_binary_operator(type_comp_addr_2, Variant::OP_EQUAL, literal_type_addr, type_string_name_addr); + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_AND, type_comp_addr_1, type_comp_addr_2); + codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_OR, type_equality_addr, type_comp_addr_1); + + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_EQUAL, p_type_addr, type_string_name_addr); + codegen.generator->write_binary_operator(type_comp_addr_2, Variant::OP_EQUAL, literal_type_addr, type_string_addr); + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_AND, type_comp_addr_1, type_comp_addr_2); + codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_OR, type_equality_addr, type_comp_addr_1); + + codegen.generator->pop_temporary(); // Remove type_comp_addr_2 from stack. + codegen.generator->pop_temporary(); // Remove type_comp_addr_1 from stack. + codegen.generator->write_and_left_operand(type_equality_addr); // Get literal. @@ -1402,7 +1410,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c GDScriptCodeGenerator::Address value_length_addr = codegen.add_temporary(temp_type); Vector<GDScriptCodeGenerator::Address> len_args; len_args.push_back(p_value_addr); - codegen.generator->write_call_gdscript_utility(value_length_addr, GDScriptUtilityFunctions::get_function("len"), len_args); + codegen.generator->write_call_gdscript_utility(value_length_addr, "len", len_args); // Test length compatibility. temp_type.builtin_type = Variant::BOOL; @@ -1500,7 +1508,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c GDScriptCodeGenerator::Address value_length_addr = codegen.add_temporary(temp_type); Vector<GDScriptCodeGenerator::Address> func_args; func_args.push_back(p_value_addr); - codegen.generator->write_call_gdscript_utility(value_length_addr, GDScriptUtilityFunctions::get_function("len"), func_args); + codegen.generator->write_call_gdscript_utility(value_length_addr, "len", func_args); // Test length compatibility. temp_type.builtin_type = Variant::BOOL; @@ -1645,7 +1653,7 @@ void GDScriptCompiler::_add_locals_in_block(CodeGen &codegen, const GDScriptPars // Parameters are added directly from function and loop variables are declared explicitly. continue; } - codegen.add_local(p_block->locals[i].name, _gdtype_from_datatype(p_block->locals[i].get_datatype())); + codegen.add_local(p_block->locals[i].name, _gdtype_from_datatype(p_block->locals[i].get_datatype(), codegen.script)); } } @@ -1671,11 +1679,10 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui case GDScriptParser::Node::MATCH: { const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(s); - gen->start_match(); codegen.start_block(); // Evaluate the match expression. - GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype())); + GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address value_expr = _parse_expression(codegen, err, match->test); if (err) { return err; @@ -1710,7 +1717,6 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui const GDScriptParser::MatchBranchNode *branch = match->branches[j]; - gen->start_match_branch(); // Need so lower level code can patch 'continue' jumps. codegen.start_block(); // Create an extra block around for binds. // Add locals in block before patterns, so temporaries don't use the stack address for binds. @@ -1748,8 +1754,6 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui for (int j = 0; j < match->branches.size(); j++) { gen->write_endif(); } - - gen->end_match(); } break; case GDScriptParser::Node::IF: { const GDScriptParser::IfNode *if_n = static_cast<const GDScriptParser::IfNode *>(s); @@ -1784,9 +1788,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui const GDScriptParser::ForNode *for_n = static_cast<const GDScriptParser::ForNode *>(s); codegen.start_block(); - GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype())); + GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype(), codegen.script)); - gen->start_for(iterator.type, _gdtype_from_datatype(for_n->list->get_datatype())); + gen->start_for(iterator.type, _gdtype_from_datatype(for_n->list->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address list = _parse_expression(codegen, err, for_n->list); if (err) { @@ -1837,12 +1841,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui gen->write_break(); } break; case GDScriptParser::Node::CONTINUE: { - const GDScriptParser::ContinueNode *cont = static_cast<const GDScriptParser::ContinueNode *>(s); - if (cont->is_for_match) { - gen->write_continue_match(); - } else { - gen->write_continue(); - } + gen->write_continue(); } break; case GDScriptParser::Node::RETURN: { const GDScriptParser::ReturnNode *return_n = static_cast<const GDScriptParser::ReturnNode *>(s); @@ -1897,13 +1896,14 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui const GDScriptParser::VariableNode *lv = static_cast<const GDScriptParser::VariableNode *>(s); // Should be already in stack when the block began. GDScriptCodeGenerator::Address local = codegen.locals[lv->identifier->name]; - GDScriptParser::DataType local_type = lv->get_datatype(); + GDScriptDataType local_type = _gdtype_from_datatype(lv->get_datatype(), codegen.script); + bool initialized = false; if (lv->initializer != nullptr) { // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. - if (local_type.is_hard_type() && local_type.builtin_type == Variant::ARRAY) { + if (local_type.has_type && local_type.builtin_type == Variant::ARRAY) { if (local_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(local, _gdtype_from_datatype(local_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); } else { codegen.generator->write_construct_array(local, Vector<GDScriptCodeGenerator::Address>()); } @@ -1920,15 +1920,23 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } - } else if (lv->get_datatype().is_hard_type()) { + initialized = true; + } else if (local_type.has_type) { // Initialize with default for type. if (local_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(local, _gdtype_from_datatype(local_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); - } else if (local_type.kind == GDScriptParser::DataType::BUILTIN) { + codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); + initialized = true; + } else if (local_type.kind == GDScriptDataType::BUILTIN) { codegen.generator->write_construct(local, local_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); + initialized = true; } // The `else` branch is for objects, in such case we leave it as `null`. } + + // Assigns a null for the unassigned variables in loops. + if (!initialized && p_block->is_loop) { + codegen.generator->write_construct(local, Variant::NIL, Vector<GDScriptCodeGenerator::Address>()); + } } break; case GDScriptParser::Node::CONSTANT: { // Local constants. @@ -2007,10 +2015,10 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ for (int i = 0; i < p_func->parameters.size(); i++) { const GDScriptParser::ParameterNode *parameter = p_func->parameters[i]; GDScriptDataType par_type = _gdtype_from_datatype(parameter->get_datatype(), p_script); - uint32_t par_addr = codegen.generator->add_parameter(parameter->identifier->name, parameter->default_value != nullptr, par_type); + uint32_t par_addr = codegen.generator->add_parameter(parameter->identifier->name, parameter->initializer != nullptr, par_type); codegen.parameters[parameter->identifier->name] = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::FUNCTION_PARAMETER, par_addr, par_type); - if (p_func->parameters[i]->default_value != nullptr) { + if (parameter->initializer != nullptr) { optional_parameters++; } } @@ -2033,17 +2041,17 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ continue; } - GDScriptParser::DataType field_type = field->get_datatype(); + GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script); - GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, _gdtype_from_datatype(field->get_datatype())); + GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type); if (field->initializer) { // Emit proper line change. codegen.generator->write_newline(field->initializer->start_line); // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. - if (field_type.is_hard_type() && field_type.builtin_type == Variant::ARRAY) { + if (field_type.has_type && field_type.builtin_type == Variant::ARRAY) { if (field_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(dst_address, _gdtype_from_datatype(field_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); } else { codegen.generator->write_construct_array(dst_address, Vector<GDScriptCodeGenerator::Address>()); } @@ -2062,13 +2070,13 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } - } else if (field->get_datatype().is_hard_type()) { + } else if (field_type.has_type) { codegen.generator->write_newline(field->start_line); // Initialize with default for type. if (field_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(dst_address, _gdtype_from_datatype(field_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); - } else if (field_type.kind == GDScriptParser::DataType::BUILTIN) { + codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); + } else if (field_type.kind == GDScriptDataType::BUILTIN) { codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); } // The `else` branch is for objects, in such case we leave it as `null`. @@ -2082,13 +2090,24 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ codegen.generator->start_parameters(); for (int i = p_func->parameters.size() - optional_parameters; i < p_func->parameters.size(); i++) { const GDScriptParser::ParameterNode *parameter = p_func->parameters[i]; - GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, r_error, parameter->default_value); + GDScriptCodeGenerator::Address src_addr = _parse_expression(codegen, r_error, parameter->initializer); if (r_error) { memdelete(codegen.generator); return nullptr; } GDScriptCodeGenerator::Address dst_addr = codegen.parameters[parameter->identifier->name]; - codegen.generator->write_assign_default_parameter(dst_addr, src_addr); + + // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. + GDScriptDataType par_type = dst_addr.type; + if (par_type.has_type && par_type.builtin_type == Variant::ARRAY) { + if (par_type.has_container_element_type()) { + codegen.generator->write_construct_typed_array(dst_addr, par_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); + } else { + codegen.generator->write_construct_array(dst_addr, Vector<GDScriptCodeGenerator::Address>()); + } + } + + codegen.generator->write_assign_default_parameter(dst_addr, src_addr, parameter->use_conversion_assign); if (src_addr.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } @@ -2107,8 +2126,8 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ if (EngineDebugger::is_active()) { String signature; // Path. - if (!p_script->get_path().is_empty()) { - signature += p_script->get_path(); + if (!p_script->get_script_path().is_empty()) { + signature += p_script->get_script_path(); } // Location. if (p_func) { @@ -2195,23 +2214,20 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP return err; } -Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { - parsing_classes.insert(p_script); +Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { + if (parsed_classes.has(p_script)) { + return OK; + } - if (p_class->outer && p_class->outer->outer) { - // Owner is not root - if (!parsed_classes.has(p_script->_owner)) { - if (parsing_classes.has(p_script->_owner)) { - _set_error("Cyclic class reference for '" + String(p_class->identifier->name) + "'.", p_class); - return ERR_PARSE_ERROR; - } - Error err = _parse_class_level(p_script->_owner, p_class->outer, p_keep_state); - if (err) { - return err; - } - } + if (parsing_classes.has(p_script)) { + String class_name = p_class->identifier ? String(p_class->identifier->name) : p_class->fqcn; + _set_error(vformat(R"(Cyclic class reference for "%s".)", class_name), p_class); + return ERR_PARSE_ERROR; } + parsing_classes.insert(p_script); + + p_script->clearing = true; #ifdef TOOLS_ENABLED p_script->doc_functions.clear(); p_script->doc_variables.clear(); @@ -2234,10 +2250,24 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->base = Ref<GDScript>(); p_script->_base = nullptr; p_script->members.clear(); + + // This makes possible to clear script constants and member_functions without heap-use-after-free errors. + HashMap<StringName, Variant> constants; + for (const KeyValue<StringName, Variant> &E : p_script->constants) { + constants.insert(E.key, E.value); + } p_script->constants.clear(); + constants.clear(); + HashMap<StringName, GDScriptFunction *> member_functions; for (const KeyValue<StringName, GDScriptFunction *> &E : p_script->member_functions) { + member_functions.insert(E.key, E.value); + } + p_script->member_functions.clear(); + for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { memdelete(E.value); } + member_functions.clear(); + if (p_script->implicit_initializer) { memdelete(p_script->implicit_initializer); } @@ -2252,8 +2282,9 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->implicit_initializer = nullptr; p_script->implicit_ready = nullptr; + p_script->clearing = false; + p_script->tool = parser->is_tool(); - p_script->name = p_class->identifier ? p_class->identifier->name : ""; if (!p_script->name.is_empty()) { if (ClassDB::class_exists(p_script->name) && ClassDB::is_class_exposed(p_script->name)) { @@ -2262,53 +2293,50 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar } } - Ref<GDScriptNativeClass> native; + GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type, p_script); - GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type); // Inheritance switch (base_type.kind) { case GDScriptDataType::NATIVE: { int native_idx = GDScriptLanguage::get_singleton()->get_global_map()[base_type.native_type]; - native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx]; - ERR_FAIL_COND_V(native.is_null(), ERR_BUG); - p_script->native = native; + p_script->native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx]; + ERR_FAIL_COND_V(p_script->native.is_null(), ERR_BUG); } break; case GDScriptDataType::GDSCRIPT: { Ref<GDScript> base = Ref<GDScript>(base_type.script_type); - p_script->base = base; - p_script->_base = base.ptr(); + if (base.is_null()) { + return ERR_COMPILATION_FAILED; + } - if (p_class->base_type.kind == GDScriptParser::DataType::CLASS && p_class->base_type.class_type != nullptr) { - if (p_class->base_type.script_path == main_script->path) { - if (!parsed_classes.has(p_script->_base)) { - if (parsing_classes.has(p_script->_base)) { - String class_name = p_class->identifier ? p_class->identifier->name : "<main>"; - _set_error("Cyclic class reference for '" + class_name + "'.", p_class); - return ERR_PARSE_ERROR; - } - Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state); - if (err) { - return err; - } - } - } else { - Error err = OK; - base = GDScriptCache::get_full_script(p_class->base_type.script_path, err, main_script->path); - if (err) { - return err; - } - if (base.is_null() || !base->is_valid()) { - return ERR_COMPILATION_FAILED; - } + if (main_script->has_class(base.ptr())) { + Error err = _populate_class_members(base.ptr(), p_class->base_type.class_type, p_keep_state); + if (err) { + return err; + } + } else if (!base->is_valid()) { + Error err = OK; + Ref<GDScript> base_root = GDScriptCache::get_full_script(base->path, err, p_script->path); + if (err) { + _set_error(vformat(R"(Could not compile base class "%s" from "%s": %s)", base->fully_qualified_name, base->path, error_names[err]), nullptr); + return err; } + if (base_root.is_valid()) { + base = Ref<GDScript>(base_root->find_class(base->fully_qualified_name)); + } + if (base.is_null()) { + _set_error(vformat(R"(Could not find class "%s" in "%s".)", base->fully_qualified_name, base->path), nullptr); + return ERR_COMPILATION_FAILED; + } + ERR_FAIL_COND_V(!base->is_valid() && !base->reloading, ERR_BUG); } + p_script->base = base; + p_script->_base = base.ptr(); p_script->member_indices = base->member_indices; - native = base->native; - p_script->native = native; + p_script->native = base->native; } break; default: { - _set_error("Parser bug: invalid inheritance.", p_class); + _set_error("Parser bug: invalid inheritance.", nullptr); return ERR_BUG; } break; } @@ -2370,6 +2398,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar #ifdef TOOLS_ENABLED if (variable->initializer != nullptr && variable->initializer->is_constant) { p_script->member_default_values[name] = variable->initializer->reduced_value; + GDScriptCompiler::convert_to_initializer_type(p_script->member_default_values[name], variable); } else { p_script->member_default_values.erase(name); } @@ -2428,27 +2457,20 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar case GDScriptParser::ClassNode::Member::ENUM: { const GDScriptParser::EnumNode *enum_n = member.m_enum; + StringName name = enum_n->identifier->name; - // TODO: Make enums not be just a dictionary? - Dictionary new_enum; - for (int j = 0; j < enum_n->values.size(); j++) { - int value = enum_n->values[j].value; - // Needs to be string because Variant::get will convert to String. - new_enum[String(enum_n->values[j].identifier->name)] = value; - } - - p_script->constants.insert(enum_n->identifier->name, new_enum); + p_script->constants.insert(name, enum_n->dictionary); #ifdef TOOLS_ENABLED - p_script->member_lines[enum_n->identifier->name] = enum_n->start_line; - p_script->doc_enums[enum_n->identifier->name] = DocData::EnumDoc(); - p_script->doc_enums[enum_n->identifier->name].name = enum_n->identifier->name; - p_script->doc_enums[enum_n->identifier->name].description = enum_n->doc_description; + p_script->member_lines[name] = enum_n->start_line; + p_script->doc_enums[name] = DocData::EnumDoc(); + p_script->doc_enums[name].name = name; + p_script->doc_enums[name].description = enum_n->doc_description; for (int j = 0; j < enum_n->values.size(); j++) { DocData::ConstantDoc const_doc; const_doc.name = enum_n->values[j].identifier->name; const_doc.value = Variant(enum_n->values[j].value).operator String(); const_doc.description = enum_n->values[j].doc_description; - p_script->doc_enums[enum_n->identifier->name].values.push_back(const_doc); + p_script->doc_enums[name].values.push_back(const_doc); } #endif } break; @@ -2479,8 +2501,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar parsed_classes.insert(p_script); parsing_classes.erase(p_script); - //parse sub-classes - + // Populate sub-classes. for (int i = 0; i < p_class->members.size(); i++) { const GDScriptParser::ClassNode::Member &member = p_class->members[i]; if (member.type != member.CLASS) { @@ -2492,27 +2513,24 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar GDScript *subclass_ptr = subclass.ptr(); // Subclass might still be parsing, just skip it - if (!parsed_classes.has(subclass_ptr) && !parsing_classes.has(subclass_ptr)) { - Error err = _parse_class_level(subclass_ptr, inner_class, p_keep_state); + if (!parsing_classes.has(subclass_ptr)) { + Error err = _populate_class_members(subclass_ptr, inner_class, p_keep_state); if (err) { return err; } } #ifdef TOOLS_ENABLED - p_script->member_lines[name] = inner_class->start_line; #endif - p_script->constants.insert(name, subclass); //once parsed, goes to the list of constants } return OK; } -Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { - //parse methods - +Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { + // Compile member functions, getters, and setters. for (int i = 0; i < p_class->members.size(); i++) { const GDScriptParser::ClassNode::Member &member = p_class->members[i]; if (member.type == member.FUNCTION) { @@ -2616,17 +2634,36 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa StringName name = inner_class->identifier->name; GDScript *subclass = p_script->subclasses[name].ptr(); - Error err = _parse_class_blocks(subclass, inner_class, p_keep_state); + Error err = _compile_class(subclass, inner_class, p_keep_state); if (err) { return err; } } + p_script->_init_rpc_methods_properties(); + p_script->valid = true; return OK; } -void GDScriptCompiler::_make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { +void GDScriptCompiler::convert_to_initializer_type(Variant &p_variant, const GDScriptParser::VariableNode *p_node) { + // Set p_variant to the value of p_node's initializer, with the type of p_node's variable. + GDScriptParser::DataType member_t = p_node->datatype; + GDScriptParser::DataType init_t = p_node->initializer->datatype; + if (member_t.is_hard_type() && init_t.is_hard_type() && + member_t.kind == GDScriptParser::DataType::BUILTIN && init_t.kind == GDScriptParser::DataType::BUILTIN) { + if (Variant::can_convert_strict(init_t.builtin_type, member_t.builtin_type)) { + Variant *v = &p_node->initializer->reduced_value; + Callable::CallError ce; + Variant::construct(member_t.builtin_type, p_variant, const_cast<const Variant **>(&v), 1, ce); + } + } +} + +void GDScriptCompiler::make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { + p_script->fully_qualified_name = p_class->fqcn; + p_script->name = p_class->identifier ? p_class->identifier->name : ""; + HashMap<StringName, Ref<GDScript>> old_subclasses; if (p_keep_state) { @@ -2643,24 +2680,22 @@ void GDScriptCompiler::_make_scripts(GDScript *p_script, const GDScriptParser::C StringName name = inner_class->identifier->name; Ref<GDScript> subclass; - String fully_qualified_name = p_script->fully_qualified_name + "::" + name; if (old_subclasses.has(name)) { subclass = old_subclasses[name]; } else { - Ref<GDScript> orphan_subclass = GDScriptLanguage::get_singleton()->get_orphan_subclass(fully_qualified_name); - if (orphan_subclass.is_valid()) { - subclass = orphan_subclass; - } else { - subclass.instantiate(); - } + subclass = GDScriptLanguage::get_singleton()->get_orphan_subclass(inner_class->fqcn); + } + + if (subclass.is_null()) { + subclass.instantiate(); } subclass->_owner = p_script; - subclass->fully_qualified_name = fully_qualified_name; + subclass->path = p_script->path; p_script->subclasses.insert(name, subclass); - _make_scripts(subclass.ptr(), inner_class, false); + make_scripts(subclass.ptr(), inner_class, p_keep_state); } } @@ -2674,26 +2709,26 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri source = p_script->get_path(); - // The best fully qualified name for a base level script is its file path - p_script->fully_qualified_name = p_script->path; - // Create scripts for subclasses beforehand so they can be referenced - _make_scripts(p_script, root, p_keep_state); + make_scripts(p_script, root, p_keep_state); - p_script->_owner = nullptr; - Error err = _parse_class_level(p_script, root, p_keep_state); + main_script->_owner = nullptr; + Error err = _populate_class_members(main_script, parser->get_tree(), p_keep_state); if (err) { return err; } - err = _parse_class_blocks(p_script, root, p_keep_state); - + err = _compile_class(main_script, root, p_keep_state); if (err) { return err; } - return GDScriptCache::finish_compiling(p_script->get_path()); +#ifdef TOOLS_ENABLED + p_script->_update_doc(); +#endif + + return GDScriptCache::finish_compiling(main_script->get_path()); } String GDScriptCompiler::get_error() const { diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index e4264ea55b..17c6cc8d2f 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_compiler.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_compiler.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_COMPILER_H #define GDSCRIPT_COMPILER_H @@ -115,13 +115,14 @@ class GDScriptCompiler { bool _is_class_member_property(CodeGen &codegen, const StringName &p_name); bool _is_class_member_property(GDScript *owner, const StringName &p_name); + bool _is_local_or_parameter(CodeGen &codegen, const StringName &p_name); void _set_error(const String &p_error, const GDScriptParser::Node *p_node); Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); - GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner = nullptr) const; + GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner); GDScriptCodeGenerator::Address _parse_assign_right_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::AssignmentNode *p_assignmentint, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); @@ -130,9 +131,8 @@ class GDScriptCompiler { Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true); GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = false); Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter); - Error _parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); - Error _parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); - void _make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + Error _populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + Error _compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); int err_line = 0; int err_column = 0; StringName source; @@ -140,6 +140,8 @@ class GDScriptCompiler { bool within_await = false; public: + static void convert_to_initializer_type(Variant &p_variant, const GDScriptParser::VariableNode *p_node); + static void make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); Error compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state = false); String get_error() const; diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index b5a209c805..b5c8a6f478 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_disassembler.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_disassembler.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifdef DEBUG_ENABLED @@ -104,8 +104,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += ": "; // This makes the compiler complain if some opcode is unchecked in the switch. - Opcode opcode = Opcode(_code_ptr[ip] & INSTR_MASK); - int instr_var_args = (_code_ptr[ip] & INSTR_ARGS_MASK) >> INSTR_BITS; + Opcode opcode = Opcode(_code_ptr[ip]); switch (opcode) { case OPCODE_OPERATOR: { @@ -129,7 +128,9 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += DADDR(3); text += " = "; text += DADDR(1); - text += " <operator function> "; + text += " "; + text += operator_names[_code_ptr[ip + 4]]; + text += " "; text += DADDR(2); incr += 5; @@ -231,7 +232,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += "set_named validated "; text += DADDR(1); text += "[\""; - text += "<unknown name>"; + text += setter_names[_code_ptr[ip + 3]]; text += "\"] = "; text += DADDR(2); @@ -254,7 +255,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += " = "; text += DADDR(1); text += "[\""; - text += "<unknown name>"; + text += getter_names[_code_ptr[ip + 3]]; text += "\"]"; incr += 4; @@ -372,6 +373,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 4; } break; case OPCODE_CONSTRUCT: { + int instr_var_args = _code_ptr[++ip]; Variant::Type t = Variant::Type(_code_ptr[ip + 3 + instr_var_args]); int argc = _code_ptr[ip + 1 + instr_var_args]; @@ -391,13 +393,15 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr = 3 + instr_var_args; } break; case OPCODE_CONSTRUCT_VALIDATED: { + int instr_var_args = _code_ptr[++ip]; int argc = _code_ptr[ip + 1 + instr_var_args]; text += "construct validated "; text += DADDR(1 + argc); text += " = "; - text += "<unknown type>("; + text += constructors_names[_code_ptr[ip + 3 + argc]]; + text += "("; for (int i = 0; i < argc; i++) { if (i > 0) { text += ", "; @@ -409,6 +413,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr = 3 + instr_var_args; } break; case OPCODE_CONSTRUCT_ARRAY: { + int instr_var_args = _code_ptr[++ip]; int argc = _code_ptr[ip + 1 + instr_var_args]; text += " make_array "; text += DADDR(1 + argc); @@ -426,6 +431,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 3 + argc; } break; case OPCODE_CONSTRUCT_TYPED_ARRAY: { + int instr_var_args = _code_ptr[++ip]; int argc = _code_ptr[ip + 1 + instr_var_args]; Ref<Script> script_type = get_constant(_code_ptr[ip + argc + 2]); @@ -460,6 +466,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 3 + argc; } break; case OPCODE_CONSTRUCT_DICTIONARY: { + int instr_var_args = _code_ptr[++ip]; int argc = _code_ptr[ip + 1 + instr_var_args]; text += "make_dict "; text += DADDR(1 + argc * 2); @@ -481,8 +488,10 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { case OPCODE_CALL: case OPCODE_CALL_RETURN: case OPCODE_CALL_ASYNC: { - bool ret = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_RETURN; - bool async = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_ASYNC; + bool ret = (_code_ptr[ip]) == OPCODE_CALL_RETURN; + bool async = (_code_ptr[ip]) == OPCODE_CALL_ASYNC; + + int instr_var_args = _code_ptr[++ip]; if (ret) { text += "call-ret "; @@ -513,7 +522,8 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { } break; case OPCODE_CALL_METHOD_BIND: case OPCODE_CALL_METHOD_BIND_RET: { - bool ret = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_METHOD_BIND_RET; + bool ret = (_code_ptr[ip]) == OPCODE_CALL_METHOD_BIND_RET; + int instr_var_args = _code_ptr[++ip]; if (ret) { text += "call-method_bind-ret "; @@ -543,6 +553,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr = 5 + argc; } break; case OPCODE_CALL_BUILTIN_STATIC: { + int instr_var_args = _code_ptr[++ip]; Variant::Type type = (Variant::Type)_code_ptr[ip + 1 + instr_var_args]; int argc = _code_ptr[ip + 3 + instr_var_args]; @@ -565,6 +576,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 5 + argc; } break; case OPCODE_CALL_NATIVE_STATIC: { + int instr_var_args = _code_ptr[++ip]; MethodBind *method = _methods_ptr[_code_ptr[ip + 1 + instr_var_args]]; int argc = _code_ptr[ip + 2 + instr_var_args]; @@ -587,6 +599,8 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 4 + argc; } break; case OPCODE_CALL_PTRCALL_NO_RETURN: { + int instr_var_args = _code_ptr[++ip]; + text += "call-ptrcall (no return) "; MethodBind *method = _methods_ptr[_code_ptr[ip + 2 + instr_var_args]]; @@ -610,6 +624,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { #define DISASSEMBLE_PTRCALL(m_type) \ case OPCODE_CALL_PTRCALL_##m_type: { \ + int instr_var_args = _code_ptr[++ip]; \ text += "call-ptrcall (return "; \ text += #m_type; \ text += ") "; \ @@ -667,6 +682,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { DISASSEMBLE_PTRCALL(PACKED_COLOR_ARRAY); case OPCODE_CALL_BUILTIN_TYPE_VALIDATED: { + int instr_var_args = _code_ptr[++ip]; int argc = _code_ptr[ip + 1 + instr_var_args]; text += "call-builtin-method validated "; @@ -674,7 +690,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { text += DADDR(2 + argc) + " = "; text += DADDR(1) + "."; - text += "<unknown method>"; + text += builtin_methods_names[_code_ptr[ip + 4 + argc]]; text += "("; @@ -689,6 +705,8 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr = 5 + argc; } break; case OPCODE_CALL_UTILITY: { + int instr_var_args = _code_ptr[++ip]; + text += "call-utility "; int argc = _code_ptr[ip + 1 + instr_var_args]; @@ -708,12 +726,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr = 4 + argc; } break; case OPCODE_CALL_UTILITY_VALIDATED: { - text += "call-utility "; + int instr_var_args = _code_ptr[++ip]; + + text += "call-utility validated "; int argc = _code_ptr[ip + 1 + instr_var_args]; text += DADDR(1 + argc) + " = "; - text += "<unknown function>"; + text += utilities_names[_code_ptr[ip + 3 + argc]]; text += "("; for (int i = 0; i < argc; i++) { @@ -727,12 +747,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr = 4 + argc; } break; case OPCODE_CALL_GDSCRIPT_UTILITY: { - text += "call-gscript-utility "; + int instr_var_args = _code_ptr[++ip]; + + text += "call-gdscript-utility "; int argc = _code_ptr[ip + 1 + instr_var_args]; text += DADDR(1 + argc) + " = "; - text += "<unknown function>"; + text += gds_utilities_names[_code_ptr[ip + 3 + argc]]; text += "("; for (int i = 0; i < argc; i++) { @@ -746,6 +768,8 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr = 4 + argc; } break; case OPCODE_CALL_SELF_BASE: { + int instr_var_args = _code_ptr[++ip]; + text += "call-self-base "; int argc = _code_ptr[ip + 1 + instr_var_args]; @@ -777,6 +801,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr = 2; } break; case OPCODE_CREATE_LAMBDA: { + int instr_var_args = _code_ptr[++ip]; int captures_count = _code_ptr[ip + 1 + instr_var_args]; GDScriptFunction *lambda = _lambdas_ptr[_code_ptr[ip + 2 + instr_var_args]]; @@ -796,6 +821,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr = 3 + captures_count; } break; case OPCODE_CREATE_SELF_LAMBDA: { + int instr_var_args = _code_ptr[++ip]; int captures_count = _code_ptr[ip + 1 + instr_var_args]; GDScriptFunction *lambda = _lambdas_ptr[_code_ptr[ip + 2 + instr_var_args]]; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 3c68993b36..f88ac581ca 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_editor.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_editor.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript.h" @@ -80,7 +80,7 @@ Ref<Script> GDScriptLanguage::make_template(const String &p_template, const Stri } processed_template = processed_template.replace("_BASE_", p_base_class_name) - .replace("_CLASS_", p_class_name) + .replace("_CLASS_", p_class_name.to_pascal_case()) .replace("_TS_", _get_indentation()); scr->set_source_code(processed_template); return scr; @@ -683,37 +683,37 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio arghint += par->identifier->name.operator String() + ": " + par->get_datatype().to_string(); } - if (par->default_value) { + if (par->initializer) { String def_val = "<unknown>"; - switch (par->default_value->type) { + switch (par->initializer->type) { case GDScriptParser::Node::LITERAL: { - const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(par->default_value); + const GDScriptParser::LiteralNode *literal = static_cast<const GDScriptParser::LiteralNode *>(par->initializer); def_val = literal->value.get_construct_string(); } break; case GDScriptParser::Node::IDENTIFIER: { - const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(par->default_value); + const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(par->initializer); def_val = id->name.operator String(); } break; case GDScriptParser::Node::CALL: { - const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(par->default_value); + const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(par->initializer); if (call->is_constant && call->reduced) { def_val = call->function_name.operator String() + call->reduced_value.operator String(); } } break; case GDScriptParser::Node::ARRAY: { - const GDScriptParser::ArrayNode *arr = static_cast<const GDScriptParser::ArrayNode *>(par->default_value); + const GDScriptParser::ArrayNode *arr = static_cast<const GDScriptParser::ArrayNode *>(par->initializer); if (arr->is_constant && arr->reduced) { def_val = arr->reduced_value.operator String(); } } break; case GDScriptParser::Node::DICTIONARY: { - const GDScriptParser::DictionaryNode *dict = static_cast<const GDScriptParser::DictionaryNode *>(par->default_value); + const GDScriptParser::DictionaryNode *dict = static_cast<const GDScriptParser::DictionaryNode *>(par->initializer); if (dict->is_constant && dict->reduced) { def_val = dict->reduced_value.operator String(); } } break; case GDScriptParser::Node::SUBSCRIPT: { - const GDScriptParser::SubscriptNode *sub = static_cast<const GDScriptParser::SubscriptNode *>(par->default_value); + const GDScriptParser::SubscriptNode *sub = static_cast<const GDScriptParser::SubscriptNode *>(par->initializer); if (sub->is_constant) { if (sub->datatype.kind == GDScriptParser::DataType::ENUM) { def_val = sub->get_datatype().to_string(); @@ -782,6 +782,7 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a } } else if (p_annotation->name == SNAME("@export_node_path")) { ScriptLanguage::CodeCompletionOption node("Node", ScriptLanguage::CODE_COMPLETION_KIND_CLASS); + node.insert_text = node.display.quote(p_quote_style); r_result.insert(node.display, node); List<StringName> node_types; ClassDB::get_inheriters_from_class("Node", &node_types); @@ -790,11 +791,13 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a continue; } ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS); + option.insert_text = option.display.quote(p_quote_style); r_result.insert(option.display, option); } } else if (p_annotation->name == SNAME("@warning_ignore")) { for (int warning_code = 0; warning_code < GDScriptWarning::WARNING_MAX; warning_code++) { ScriptLanguage::CodeCompletionOption warning(GDScriptWarning::get_name_from_code((GDScriptWarning::Code)warning_code).to_lower(), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); + warning.insert_text = warning.display.quote(p_quote_style); r_result.insert(warning.display, warning); } } @@ -995,9 +998,8 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base ERR_FAIL_COND(p_recursion_depth > COMPLETION_RECURSION_LIMIT); GDScriptParser::DataType base_type = p_base.type; - bool _static = base_type.is_meta_type; - if (_static && base_type.kind != GDScriptParser::DataType::BUILTIN) { + if (base_type.is_meta_type && base_type.kind != GDScriptParser::DataType::BUILTIN && base_type.kind != GDScriptParser::DataType::ENUM) { ScriptLanguage::CodeCompletionOption option("new", ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, ScriptLanguage::LOCATION_LOCAL); option.insert_text += "("; r_result.insert(option.display, option); @@ -1006,7 +1008,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base while (!base_type.has_no_type()) { switch (base_type.kind) { case GDScriptParser::DataType::CLASS: { - _find_identifiers_in_class(base_type.class_type, p_only_functions, _static, false, r_result, p_recursion_depth + 1); + _find_identifiers_in_class(base_type.class_type, p_only_functions, base_type.is_meta_type, false, r_result, p_recursion_depth + 1); // This already finds all parent identifiers, so we are done. base_type = GDScriptParser::DataType(); } break; @@ -1014,7 +1016,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base Ref<Script> scr = base_type.script_type; if (scr.is_valid()) { if (!p_only_functions) { - if (!_static) { + if (!base_type.is_meta_type) { List<PropertyInfo> members; scr->get_script_property_list(&members); for (const PropertyInfo &E : members) { @@ -1090,7 +1092,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base r_result.insert(option.display, option); } - if (!_static || Engine::get_singleton()->has_singleton(type)) { + if (!base_type.is_meta_type || Engine::get_singleton()->has_singleton(type)) { List<PropertyInfo> pinfo; ClassDB::get_property_list(type, &pinfo); for (const PropertyInfo &E : pinfo) { @@ -1107,7 +1109,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base } } - bool only_static = _static && !Engine::get_singleton()->has_singleton(type); + bool only_static = base_type.is_meta_type && !Engine::get_singleton()->has_singleton(type); List<MethodInfo> methods; ClassDB::get_method_list(type, &methods, false, true); @@ -1129,6 +1131,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base } return; } break; + case GDScriptParser::DataType::ENUM: case GDScriptParser::DataType::BUILTIN: { Callable::CallError err; Variant tmp; @@ -1156,6 +1159,10 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base List<MethodInfo> methods; tmp.get_method_list(&methods); for (const MethodInfo &E : methods) { + if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type && !(E.flags & METHOD_FLAG_CONST)) { + // Enum types are static and cannot change, therefore we skip non-const dictionary methods. + continue; + } ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); if (E.arguments.size()) { option.insert_text += "("; @@ -1271,6 +1278,14 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context } r_result.insert(option.display, option); } + + // Global classes + List<StringName> global_classes; + ScriptServer::get_global_class_list(&global_classes); + for (const StringName &E : global_classes) { + ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_OTHER_USER_CODE); + r_result.insert(option.display, option); + } } static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value) { @@ -1364,6 +1379,9 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, if (p_expression->is_constant) { // Already has a value, so just use that. r_type = _type_from_variant(p_expression->reduced_value); + if (p_expression->get_datatype().kind == GDScriptParser::DataType::ENUM) { + r_type.type = p_expression->get_datatype(); + } found = true; } else { switch (p_expression->type) { @@ -1612,7 +1630,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, } } - if (!found) { + if (!found && base.value.get_type() != Variant::NIL) { found = _guess_method_return_type_from_base(c, base, call->function_name, r_type); } } @@ -1856,9 +1874,9 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } break; case GDScriptParser::SuiteNode::Local::PARAMETER: - if (local.parameter->default_value) { - last_assign_line = local.parameter->default_value->end_line; - last_assigned_expression = local.parameter->default_value; + if (local.parameter->initializer) { + last_assign_line = local.parameter->initializer->end_line; + last_assigned_expression = local.parameter->initializer; } is_function_parameter = true; break; @@ -1939,12 +1957,12 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, if ((!id_type.is_set() || id_type.is_variant()) && parameter->get_datatype().is_hard_type()) { id_type = parameter->get_datatype(); } - if (parameter->default_value) { + if (parameter->initializer) { GDScriptParser::CompletionContext c = p_context; c.current_function = parent_function; c.current_class = base_type.class_type; c.base = nullptr; - if (_guess_expression_type(c, parameter->default_value, r_type)) { + if (_guess_expression_type(c, parameter->initializer, r_type)) { return true; } } @@ -2272,6 +2290,11 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex if (base_type.class_type->has_function(p_method)) { const GDScriptParser::FunctionNode *method = base_type.class_type->get_member(p_method).function; if (!is_static || method->is_static) { + if (method->get_datatype().is_set() && !method->get_datatype().is_variant()) { + r_type.type = method->get_datatype(); + return true; + } + int last_return_line = -1; const GDScriptParser::ExpressionNode *last_returned_value = nullptr; GDScriptParser::CompletionContext c = p_context; @@ -2285,10 +2308,6 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex if (_guess_expression_type(c, last_returned_value, r_type)) { return true; } - if (method->get_datatype().is_set() && !method->get_datatype().is_variant()) { - r_type.type = method->get_datatype(); - return true; - } } } } @@ -2511,9 +2530,72 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } } +static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::SubscriptNode *p_subscript, GDScriptParser::DataType &r_base_type, Variant *r_base = nullptr) { + if (p_context.base == nullptr) { + return false; + } + const GDScriptParser::GetNodeNode *get_node = nullptr; + + switch (p_subscript->base->type) { + case GDScriptParser::Node::GET_NODE: { + get_node = static_cast<GDScriptParser::GetNodeNode *>(p_subscript->base); + } break; + + case GDScriptParser::Node::IDENTIFIER: { + const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base); + + switch (identifier_node->source) { + case GDScriptParser::IdentifierNode::Source::MEMBER_VARIABLE: { + if (p_context.current_class != nullptr) { + const StringName &member_name = identifier_node->name; + const GDScriptParser::ClassNode *current_class = p_context.current_class; + + if (current_class->has_member(member_name)) { + const GDScriptParser::ClassNode::Member &member = current_class->get_member(member_name); + + if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { + const GDScriptParser::VariableNode *variable = static_cast<GDScriptParser::VariableNode *>(member.variable); + + if (variable->initializer && variable->initializer->type == GDScriptParser::Node::GET_NODE) { + get_node = static_cast<GDScriptParser::GetNodeNode *>(variable->initializer); + } + } + } + } + } break; + case GDScriptParser::IdentifierNode::Source::LOCAL_VARIABLE: { + if (identifier_node->next != nullptr && identifier_node->next->type == GDScriptParser::ClassNode::Node::GET_NODE) { + get_node = static_cast<GDScriptParser::GetNodeNode *>(identifier_node->next); + } + } break; + default: { + } break; + } + } break; + default: { + } break; + } + + if (get_node != nullptr) { + const Object *node = p_context.base->call("get_node_or_null", NodePath(get_node->full_path)); + if (node != nullptr) { + if (r_base != nullptr) { + *r_base = node; + } + r_base_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_base_type.kind = GDScriptParser::DataType::NATIVE; + r_base_type.native_type = node->get_class_name(); + r_base_type.builtin_type = Variant::OBJECT; + return true; + } + } + + return false; +} + static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, const GDScriptParser::Node *p_call, int p_argidx, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, bool &r_forced, String &r_arghint) { if (p_call->type == GDScriptParser::Node::PRELOAD) { - if (p_argidx == 0 && bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) { + if (p_argidx == 0 && bool(EDITOR_GET("text_editor/completion/complete_file_paths"))) { _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), r_result); } @@ -2562,12 +2644,16 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } if (subscript->is_attribute) { - GDScriptCompletionIdentifier ci; - if (_guess_expression_type(p_context, subscript->base, ci)) { - base_type = ci.type; - base = ci.value; - } else { - return; + bool found_type = _get_subscript_type(p_context, subscript, base_type, &base); + + if (!found_type) { + GDScriptCompletionIdentifier ci; + if (_guess_expression_type(p_context, subscript->base, ci)) { + base_type = ci.type; + base = ci.value; + } else { + return; + } } _static = base_type.is_meta_type; @@ -2765,7 +2851,8 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c const GDScriptParser::SubscriptNode *attr = static_cast<const GDScriptParser::SubscriptNode *>(completion_context.node); if (attr->base) { GDScriptCompletionIdentifier base; - if (!_guess_expression_type(completion_context, attr->base, base)) { + bool found_type = _get_subscript_type(completion_context, attr, base.type); + if (!found_type && !_guess_expression_type(completion_context, attr->base, base)) { break; } @@ -2779,16 +2866,6 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c break; } - GDScriptParser::CompletionContext c = completion_context; - c.current_function = nullptr; - c.current_suite = nullptr; - c.base = base.value.get_type() == Variant::OBJECT ? base.value.operator Object *() : nullptr; - if (base.type.kind == GDScriptParser::DataType::CLASS) { - c.current_class = base.type.class_type; - } else { - c.current_class = nullptr; - } - _find_identifiers_in_base(base, false, options, 0); } break; case GDScriptParser::COMPLETION_TYPE_ATTRIBUTE: { @@ -2820,7 +2897,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c r_forced = true; } break; case GDScriptParser::COMPLETION_RESOURCE_PATH: { - if (EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths")) { + if (EDITOR_GET("text_editor/completion/complete_file_paths")) { _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), options); r_forced = true; } @@ -2918,7 +2995,9 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c // 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. + // Ignore quote_style and just use double quotes for paths with apostrophes. + // Double quotes don't need to be checked because they're not valid in node and property names. + opt = opt.quote(opt.contains("'") ? "\"" : quote_style); // Handle user preference. } ScriptLanguage::CodeCompletionOption option(opt, ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH); options.insert(option.display, option); @@ -3051,8 +3130,9 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION; r_result.location = base_type.class_type->get_member(p_symbol).get_line(); r_result.class_path = base_type.script_path; - r_result.script = GDScriptCache::get_shallow_script(r_result.class_path); - return OK; + Error err = OK; + r_result.script = GDScriptCache::get_shallow_script(r_result.class_path, err); + return err; } base_type = base_type.class_type->base_type; } @@ -3208,15 +3288,6 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } } - // Need special checks for assert and preload as they are technically - // keywords, so are not registered in GDScriptUtilityFunctions. - if (GDScriptUtilityFunctions::function_exists(p_symbol) || "assert" == p_symbol || "preload" == p_symbol) { - r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; - r_result.class_name = "@GDScript"; - r_result.class_member = p_symbol; - return OK; - } - if ("PI" == p_symbol || "TAU" == p_symbol || "INF" == p_symbol || "NAN" == p_symbol) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; r_result.class_name = "@GDScript"; @@ -3226,10 +3297,24 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co GDScriptParser parser; parser.parse(p_code, p_path, true); - GDScriptAnalyzer analyzer(&parser); - analyzer.analyze(); GDScriptParser::CompletionContext context = parser.get_completion_context(); + context.base = p_owner; + + // Allows class functions with the names like built-ins to be handled properly. + if (context.type != GDScriptParser::COMPLETION_ATTRIBUTE) { + // Need special checks for assert and preload as they are technically + // keywords, so are not registered in GDScriptUtilityFunctions. + if (GDScriptUtilityFunctions::function_exists(p_symbol) || "assert" == p_symbol || "preload" == p_symbol) { + r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; + r_result.class_name = "@GDScript"; + r_result.class_member = p_symbol; + return OK; + } + } + + GDScriptAnalyzer analyzer(&parser); + analyzer.analyze(); if (context.current_class && context.current_class->extends.size() > 0) { bool success = false; @@ -3387,7 +3472,9 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co break; } GDScriptCompletionIdentifier base; - if (!_guess_expression_type(context, subscript->base, base)) { + + bool found_type = _get_subscript_type(context, subscript, base.type); + if (!found_type && !_guess_expression_type(context, subscript->base, base)) { break; } diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 98b3e40f1b..71831a3a97 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_function.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_function.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_function.h" @@ -149,10 +149,17 @@ GDScriptFunction::GDScriptFunction() { } GDScriptFunction::~GDScriptFunction() { + get_script()->member_functions.erase(name); + for (int i = 0; i < lambdas.size(); i++) { memdelete(lambdas[i]); } + for (int i = 0; i < argument_types.size(); i++) { + argument_types.write[i].script_type_ref = Ref<Script>(); + } + return_type.script_type_ref = Ref<Script>(); + #ifdef DEBUG_ENABLED MutexLock lock(GDScriptLanguage::get_singleton()->mutex); diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index e44038d6da..37416a734d 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_function.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_function.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_FUNCTION_H #define GDSCRIPT_FUNCTION_H @@ -405,6 +405,7 @@ public: ADDR_TYPE_STACK = 0, ADDR_TYPE_CONSTANT = 1, ADDR_TYPE_MEMBER = 2, + ADDR_TYPE_MAX = 3, }; enum FixedAddresses { @@ -416,12 +417,6 @@ public: ADDR_NIL = ADDR_STACK_NIL | (ADDR_TYPE_STACK << ADDR_BITS), }; - enum Instruction { - INSTR_BITS = 20, - INSTR_MASK = ((1 << INSTR_BITS) - 1), - INSTR_ARGS_MASK = ~INSTR_MASK, - }; - struct StackDebug { int line; int pos; @@ -430,6 +425,7 @@ public: }; private: + friend class GDScript; friend class GDScriptCompiler; friend class GDScriptByteCodeGenerator; @@ -509,11 +505,20 @@ private: Vector<Variant> default_arg_values; #endif +#ifdef DEBUG_ENABLED + Vector<String> operator_names; + Vector<String> setter_names; + Vector<String> getter_names; + Vector<String> builtin_methods_names; + Vector<String> constructors_names; + Vector<String> utilities_names; + Vector<String> gds_utilities_names; +#endif + List<StackDebug> stack_debug; Variant _get_default_variant_for_data_type(const GDScriptDataType &p_data_type); - _FORCE_INLINE_ Variant *_get_variant(int p_address, GDScriptInstance *p_instance, Variant *p_stack, String &r_error) const; _FORCE_INLINE_ String _get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const; friend class GDScriptLanguage; diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp index a25bf9a306..e9fe17bb17 100644 --- a/modules/gdscript/gdscript_lambda_callable.cpp +++ b/modules/gdscript/gdscript_lambda_callable.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_lambda_callable.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_lambda_callable.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_lambda_callable.h" diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h index 1954089983..33bdf6dfc1 100644 --- a/modules/gdscript/gdscript_lambda_callable.h +++ b/modules/gdscript/gdscript_lambda_callable.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_lambda_callable.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_lambda_callable.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_LAMBDA_CALLABLE_H #define GDSCRIPT_LAMBDA_CALLABLE_H diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index bdf6fb35b6..1a744d59ba 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_parser.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_parser.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_parser.h" @@ -41,6 +41,7 @@ #include "core/os/os.h" #include "core/string/string_builder.h" #include "gdscript_warning.h" +#include "servers/text_server.h" #endif // DEBUG_ENABLED #ifdef TOOLS_ENABLED @@ -147,6 +148,10 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true); // Networking. register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("", "", "", 0), true); + +#ifdef DEBUG_ENABLED + is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable"); +#endif } GDScriptParser::~GDScriptParser() { @@ -182,24 +187,6 @@ void GDScriptParser::push_error(const String &p_message, const Node *p_origin) { } #ifdef DEBUG_ENABLED -void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1, const String &p_symbol2, const String &p_symbol3, const String &p_symbol4) { - ERR_FAIL_COND(p_source == nullptr); - Vector<String> symbols; - if (!p_symbol1.is_empty()) { - symbols.push_back(p_symbol1); - } - if (!p_symbol2.is_empty()) { - symbols.push_back(p_symbol2); - } - if (!p_symbol3.is_empty()) { - symbols.push_back(p_symbol3); - } - if (!p_symbol4.is_empty()) { - symbols.push_back(p_symbol4); - } - push_warning(p_source, p_code, symbols); -} - void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols) { ERR_FAIL_COND(p_source == nullptr); if (is_ignoring_warnings) { @@ -230,7 +217,7 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_ warning.leftmost_column = p_source->leftmost_column; warning.rightmost_column = p_source->rightmost_column; - if (warn_level == GDScriptWarning::WarnLevel::ERROR) { + if (warn_level == GDScriptWarning::WarnLevel::ERROR || bool(GLOBAL_GET("debug/gdscript/warnings/treat_warnings_as_errors"))) { push_error(warning.get_message(), p_source); return; } @@ -534,44 +521,30 @@ void GDScriptParser::end_statement(const String &p_context) { void GDScriptParser::parse_program() { head = alloc_node<ClassNode>(); + head->fqcn = script_path; current_class = head; + bool can_have_class_or_extends = true; - // If we happen to parse an annotation before extends or class_name keywords, track it. - // @tool is allowed, but others should fail. - AnnotationNode *premature_annotation = nullptr; - - if (match(GDScriptTokenizer::Token::ANNOTATION)) { - // Check for @tool, script-level, or standalone annotation. + while (match(GDScriptTokenizer::Token::ANNOTATION)) { AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); if (annotation != nullptr) { - if (annotation->name == SNAME("@tool")) { - // TODO: don't allow @tool anywhere else. (Should all script annotations be the first thing?). - _is_tool = true; - if (previous.type != GDScriptTokenizer::Token::NEWLINE) { - push_error(R"(Expected newline after "@tool" annotation.)"); - } - // @tool annotation has no specific target. - annotation->apply(this, nullptr); - } else if (annotation->applies_to(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE)) { - premature_annotation = annotation; - if (previous.type != GDScriptTokenizer::Token::NEWLINE) { - push_error(R"(Expected newline after a standalone annotation.)"); - } - annotation->apply(this, head); + if (annotation->applies_to(AnnotationInfo::SCRIPT)) { + head->annotations.push_back(annotation); } else { - premature_annotation = annotation; annotation_stack.push_back(annotation); + // This annotation must appear after script-level annotations + // and class_name/extends (ex: could be @onready or @export), + // so we stop looking for script-level stuff. + can_have_class_or_extends = false; + break; } } } - for (bool should_break = false; !should_break;) { + while (can_have_class_or_extends) { // Order here doesn't matter, but there should be only one of each at most. switch (current.type) { case GDScriptTokenizer::Token::CLASS_NAME: - if (premature_annotation != nullptr) { - push_error(R"("class_name" should be used before annotations (except @tool).)"); - } advance(); if (head->identifier != nullptr) { push_error(R"("class_name" can only be used once.)"); @@ -580,9 +553,6 @@ void GDScriptParser::parse_program() { } break; case GDScriptTokenizer::Token::EXTENDS: - if (premature_annotation != nullptr) { - push_error(R"("extends" should be used before annotations (except @tool).)"); - } advance(); if (head->extends_used) { push_error(R"("extends" can only be used once.)"); @@ -592,7 +562,8 @@ void GDScriptParser::parse_program() { } break; default: - should_break = true; + // No tokens are allowed between script annotations and class/extends. + can_have_class_or_extends = false; break; } @@ -601,21 +572,6 @@ void GDScriptParser::parse_program() { } } - if (match(GDScriptTokenizer::Token::ANNOTATION)) { - // Check for a script-level, or standalone annotation. - AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL); - if (annotation != nullptr) { - if (annotation->applies_to(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE)) { - if (previous.type != GDScriptTokenizer::Token::NEWLINE) { - push_error(R"(Expected newline after a standalone annotation.)"); - } - annotation->apply(this, head); - } else { - annotation_stack.push_back(annotation); - } - } - } - parse_class_body(true); complete_extents(head); @@ -637,6 +593,53 @@ void GDScriptParser::parse_program() { clear_unused_annotations(); } +GDScriptParser::ClassNode *GDScriptParser::find_class(const String &p_qualified_name) const { + String first = p_qualified_name.get_slice("::", 0); + + Vector<String> class_names; + GDScriptParser::ClassNode *result = nullptr; + // Empty initial name means start at the head. + if (first.is_empty() || (head->identifier && first == head->identifier->name)) { + class_names = p_qualified_name.split("::"); + result = head; + } else if (p_qualified_name.begins_with(script_path)) { + // Script path could have a class path separator("::") in it. + class_names = p_qualified_name.trim_prefix(script_path).split("::"); + result = head; + } else if (head->has_member(first)) { + class_names = p_qualified_name.split("::"); + GDScriptParser::ClassNode::Member member = head->get_member(first); + if (member.type == GDScriptParser::ClassNode::Member::CLASS) { + result = member.m_class; + } + } + + // Starts at index 1 because index 0 was handled above. + for (int i = 1; result != nullptr && i < class_names.size(); i++) { + String current_name = class_names[i]; + GDScriptParser::ClassNode *next = nullptr; + if (result->has_member(current_name)) { + GDScriptParser::ClassNode::Member member = result->get_member(current_name); + if (member.type == GDScriptParser::ClassNode::Member::CLASS) { + next = member.m_class; + } + } + result = next; + } + + return result; +} + +bool GDScriptParser::has_class(const GDScriptParser::ClassNode *p_class) const { + if (head->fqcn.is_empty() && p_class->fqcn.get_slice("::", 0).is_empty()) { + return p_class == head; + } else if (p_class->fqcn.begins_with(head->fqcn)) { + return find_class(p_class->fqcn.trim_prefix(head->fqcn)) == p_class; + } + + return false; +} + GDScriptParser::ClassNode *GDScriptParser::parse_class() { ClassNode *n_class = alloc_node<ClassNode>(); @@ -646,6 +649,15 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() { if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the class name after "class".)")) { n_class->identifier = parse_identifier(); + if (n_class->outer) { + String fqcn = n_class->outer->fqcn; + if (fqcn.is_empty()) { + fqcn = script_path; + } + n_class->fqcn = fqcn + "::" + n_class->identifier->name; + } else { + n_class->fqcn = n_class->identifier->name; + } } if (match(GDScriptTokenizer::Token::EXTENDS)) { @@ -684,6 +696,7 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() { void GDScriptParser::parse_class_name() { if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the global class name after "class_name".)")) { current_class->identifier = parse_identifier(); + current_class->fqcn = String(current_class->identifier->name); } if (match(GDScriptTokenizer::Token::EXTENDS)) { @@ -758,7 +771,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)() return; } - // Apply annotations. for (AnnotationNode *&annotation : annotations) { member->annotations.push_back(annotation); } @@ -835,7 +847,7 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) { if (previous.type != GDScriptTokenizer::Token::NEWLINE) { push_error(R"(Expected newline after a standalone annotation.)"); } - annotation->apply(this, head); + head->annotations.push_back(annotation); } else { annotation_stack.push_back(annotation); } @@ -1157,7 +1169,7 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() { if (match(GDScriptTokenizer::Token::EQUAL)) { // Default value. - parameter->default_value = parse_expression(false); + parameter->initializer = parse_expression(false); } complete_extents(parameter); @@ -1188,7 +1200,7 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() { push_error("Expected signal parameter name."); break; } - if (parameter->default_value != nullptr) { + if (parameter->initializer != nullptr) { push_error(R"(Signal parameters cannot have a default value.)"); } if (signal->parameters_indices.has(parameter->identifier->name)) { @@ -1237,16 +1249,18 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { EnumNode::Value item; GDScriptParser::IdentifierNode *identifier = parse_identifier(); #ifdef DEBUG_ENABLED - for (MethodInfo &info : gdscript_funcs) { - if (info.name == identifier->name) { + if (!named) { // Named enum identifiers do not shadow anything since you can only access them with NamedEnum.ENUM_VALUE + 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"); } } - 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; @@ -1257,14 +1271,8 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum() { if (elements.has(item.identifier->name)) { push_error(vformat(R"(Name "%s" was already in this enum (at line %d).)", item.identifier->name, elements[item.identifier->name]), item.identifier); } else if (!named) { - // TODO: Abstract this recursive member check. - ClassNode *parent = current_class; - while (parent != nullptr) { - if (parent->members_indices.has(item.identifier->name)) { - push_error(vformat(R"(Name "%s" is already used as a class %s.)", item.identifier->name, parent->get_member(item.identifier->name).get_type_name())); - break; - } - parent = parent->outer; + if (current_class->members_indices.has(item.identifier->name)) { + push_error(vformat(R"(Name "%s" is already used as a class %s.)", item.identifier->name, current_class->get_member(item.identifier->name).get_type_name())); } } @@ -1333,7 +1341,7 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod if (parameter == nullptr) { break; } - if (parameter->default_value != nullptr) { + if (parameter->initializer != nullptr) { default_used = true; } else { if (default_used) { @@ -1425,7 +1433,11 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali annotation->info = &valid_annotations[annotation->name]; if (!annotation->applies_to(p_valid_targets)) { - push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name)); + if (annotation->applies_to(AnnotationInfo::SCRIPT)) { + push_error(vformat(R"(Annotation "%s" must be at the top of the script, before "extends" and "class_name".)", annotation->name)); + } else { + push_error(vformat(R"(Annotation "%s" is not allowed in this level.)", annotation->name)); + } valid = false; } @@ -1457,7 +1469,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional. if (valid) { - valid = validate_annotation_arguments(annotation); + valid = validate_annotation_argument_count(annotation); } return valid ? annotation : nullptr; @@ -1530,7 +1542,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, VariableNode *variable = static_cast<VariableNode *>(statement); const SuiteNode::Local &local = current_suite->get_local(variable->identifier->name); if (local.type != SuiteNode::Local::UNDEFINED) { - push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), variable->identifier->name)); + push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), variable->identifier->name), variable->identifier); } current_suite->add_local(variable, current_function); break; @@ -1545,7 +1557,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, } else { name = "variable"; } - push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, constant->identifier->name)); + push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, constant->identifier->name), constant->identifier); } current_suite->add_local(constant, current_function); break; @@ -1688,6 +1700,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { case Node::CALL: case Node::ASSIGNMENT: case Node::AWAIT: + case Node::TERNARY_OPERATOR: // Fine. break; case Node::LAMBDA: @@ -1703,7 +1716,6 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { } } - // Apply annotations to statement. while (!is_annotation && result != nullptr && !annotation_stack.is_empty()) { AnnotationNode *last_annotation = annotation_stack.back()->get(); if (last_annotation->applies_to(AnnotationInfo::STATEMENT)) { @@ -1737,24 +1749,29 @@ GDScriptParser::AssertNode *GDScriptParser::parse_assert() { // TODO: Add assert message. AssertNode *assert = alloc_node<AssertNode>(); + push_multiline(true); consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "assert".)"); + assert->condition = parse_expression(false); if (assert->condition == nullptr) { push_error("Expected expression to assert."); + pop_multiline(); complete_extents(assert); return nullptr; } - if (match(GDScriptTokenizer::Token::COMMA)) { - // Error message. + if (match(GDScriptTokenizer::Token::COMMA) && !check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { assert->message = parse_expression(false); if (assert->message == nullptr) { push_error(R"(Expected error message for assert after ",".)"); + pop_multiline(); complete_extents(assert); return nullptr; } + match(GDScriptTokenizer::Token::COMMA); } + pop_multiline(); consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after assert expression.)*"); complete_extents(assert); @@ -1775,11 +1792,10 @@ GDScriptParser::BreakNode *GDScriptParser::parse_break() { GDScriptParser::ContinueNode *GDScriptParser::parse_continue() { if (!can_continue) { - push_error(R"(Cannot use "continue" outside of a loop or pattern matching block.)"); + push_error(R"(Cannot use "continue" outside of a loop.)"); } current_suite->has_continue = true; ContinueNode *cont = alloc_node<ContinueNode>(); - cont->is_for_match = is_continue_match; complete_extents(cont); end_statement(R"("continue")"); return cont; @@ -1805,12 +1821,10 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { // Save break/continue state. bool could_break = can_break; bool could_continue = can_continue; - bool was_continue_match = is_continue_match; // Allow break/continue. can_break = true; can_continue = true; - is_continue_match = false; SuiteNode *suite = alloc_node<SuiteNode>(); if (n_for->variable) { @@ -1820,15 +1834,14 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { } suite->add_local(SuiteNode::Local(n_for->variable, current_function)); } - suite->parent_for = n_for; n_for->loop = parse_suite(R"("for" block)", suite); + n_for->loop->is_loop = true; complete_extents(n_for); // Reset break/continue state. can_break = could_break; can_continue = could_continue; - is_continue_match = was_continue_match; return n_for; } @@ -1965,13 +1978,6 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { return nullptr; } - // Save continue state. - bool could_continue = can_continue; - bool was_continue_match = is_continue_match; - // Allow continue for match. - can_continue = true; - is_continue_match = true; - SuiteNode *suite = alloc_node<SuiteNode>(); if (branch->patterns.size() > 0) { for (const KeyValue<StringName, IdentifierNode *> &E : branch->patterns[0]->binds) { @@ -1984,10 +1990,6 @@ GDScriptParser::MatchBranchNode *GDScriptParser::parse_match_branch() { branch->block = parse_suite("match pattern block", suite); complete_extents(branch); - // Restore continue state. - can_continue = could_continue; - is_continue_match = was_continue_match; - return branch; } @@ -2146,20 +2148,18 @@ GDScriptParser::WhileNode *GDScriptParser::parse_while() { // Save break/continue state. bool could_break = can_break; bool could_continue = can_continue; - bool was_continue_match = is_continue_match; // Allow break/continue. can_break = true; can_continue = true; - is_continue_match = false; n_while->loop = parse_suite(R"("while" block)"); + n_while->loop->is_loop = true; complete_extents(n_while); // Reset break/continue state. can_break = could_break; can_continue = could_continue; - is_continue_match = was_continue_match; return n_while; } @@ -2219,12 +2219,19 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_expression(bool p_can_assi } GDScriptParser::IdentifierNode *GDScriptParser::parse_identifier() { - return static_cast<IdentifierNode *>(parse_identifier(nullptr, false)); + IdentifierNode *identifier = static_cast<IdentifierNode *>(parse_identifier(nullptr, false)); +#ifdef DEBUG_ENABLED + // Check for spoofing here (if available in TextServer) since this isn't called inside expressions. This is only relevant for declarations. + if (identifier && TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY) && TS->spoof_check(identifier->name.operator String())) { + push_warning(identifier, GDScriptWarning::CONFUSABLE_IDENTIFIER, identifier->name.operator String()); + } +#endif + return identifier; } GDScriptParser::ExpressionNode *GDScriptParser::parse_identifier(ExpressionNode *p_previous_operand, bool p_can_assign) { if (!previous.is_identifier()) { - ERR_FAIL_V_MSG(nullptr, "Parser bug: parsing literal node without literal token."); + ERR_FAIL_V_MSG(nullptr, "Parser bug: parsing identifier node without identifier token."); } IdentifierNode *identifier = alloc_node<IdentifierNode>(); complete_extents(identifier); @@ -3589,7 +3596,7 @@ bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const { return (info->target_kind & p_target_kinds) > 0; } -bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) { +bool GDScriptParser::validate_annotation_argument_count(AnnotationNode *p_annotation) { ERR_FAIL_COND_V_MSG(!valid_annotations.has(p_annotation->name), false, vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name)); const MethodInfo &info = valid_annotations[p_annotation->name].info; @@ -3604,62 +3611,6 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) return false; } - const List<PropertyInfo>::Element *E = info.arguments.front(); - for (int i = 0; i < p_annotation->arguments.size(); i++) { - ExpressionNode *argument = p_annotation->arguments[i]; - const PropertyInfo ¶meter = E->get(); - - if (E->next() != nullptr) { - E = E->next(); - } - - switch (parameter.type) { - case Variant::STRING: - case Variant::STRING_NAME: - case Variant::NODE_PATH: - // Allow "quote-less strings", as long as they are recognized as identifiers. - if (argument->type == Node::IDENTIFIER) { - IdentifierNode *string = static_cast<IdentifierNode *>(argument); - Callable::CallError error; - Vector<Variant> args = varray(string->name); - const Variant *name = args.ptr(); - Variant r; - Variant::construct(parameter.type, r, &(name), 1, error); - 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_at(p_annotation->resolved_arguments.size() - 1); - return false; - } - break; - } - [[fallthrough]]; - default: { - if (argument->type != Node::LITERAL) { - push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); - return false; - } - - Variant value = static_cast<LiteralNode *>(argument)->value; - if (!Variant::can_convert_strict(value.get_type(), parameter.type)) { - push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name)); - return false; - } - Callable::CallError error; - const Variant *args = &value; - Variant r; - Variant::construct(parameter.type, r, &(args), 1, error); - 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_at(p_annotation->resolved_arguments.size() - 1); - return false; - } - break; - } - } - } - return true; } @@ -3725,6 +3676,32 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node // This is called after the analyzer is done finding the type, so this should be set here. DataType export_type = variable->get_datatype(); + if (p_annotation->name == SNAME("@export_range")) { + if (export_type.builtin_type == Variant::INT) { + variable->export_info.type = Variant::INT; + } + } + if (p_annotation->name == SNAME("@export_multiline")) { + if (export_type.builtin_type == Variant::ARRAY && export_type.has_container_element_type()) { + DataType inner_type = export_type.get_container_element_type(); + if (inner_type.builtin_type != Variant::STRING) { + push_error(vformat(R"("%s" annotation on arrays requires a string type but type "%s" was given instead.)", p_annotation->name.operator String(), inner_type.to_string()), variable); + return false; + } + + String hint_prefix = itos(inner_type.builtin_type) + "/" + itos(variable->export_info.hint); + variable->export_info.hint = PROPERTY_HINT_TYPE_STRING; + variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string; + variable->export_info.type = Variant::ARRAY; + + return true; + } else if (export_type.builtin_type == Variant::DICTIONARY) { + variable->export_info.type = Variant::DICTIONARY; + + return true; + } + } + if (p_annotation->name == SNAME("@export")) { if (variable->datatype_specifier == nullptr && variable->initializer == nullptr) { push_error(R"(Cannot use simple "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation); @@ -3764,27 +3741,35 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } break; case GDScriptParser::DataType::CLASS: - // Can assume type is a global GDScript class. - if (!ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) { - push_error(R"(Exported script type must extend Resource.)"); + if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) { + variable->export_info.type = Variant::OBJECT; + variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; + variable->export_info.hint_string = export_type.to_string(); + } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) { + variable->export_info.type = Variant::OBJECT; + variable->export_info.hint = PROPERTY_HINT_NODE_TYPE; + variable->export_info.hint_string = export_type.to_string(); + } else { + push_error(R"(Export type can only be built-in, a resource, a node or an enum.)", variable); return false; } - variable->export_info.type = Variant::OBJECT; - variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; - variable->export_info.hint_string = export_type.class_type->identifier->name; + break; case GDScriptParser::DataType::SCRIPT: { StringName class_name; - if (export_type.script_type != nullptr && export_type.script_type.is_valid()) { + StringName native_base; + if (export_type.script_type.is_valid()) { class_name = export_type.script_type->get_language()->get_global_class_name(export_type.script_type->get_path()); + native_base = export_type.script_type->get_instance_base_type(); } if (class_name == StringName()) { Ref<Script> script = ResourceLoader::load(export_type.script_path, SNAME("Script")); if (script.is_valid()) { class_name = script->get_language()->get_global_class_name(export_type.script_path); + native_base = script->get_instance_base_type(); } } - if (class_name != StringName() && ClassDB::is_parent_class(ScriptServer::get_global_class_native_base(class_name), SNAME("Resource"))) { + if (class_name != StringName() && native_base != StringName() && ClassDB::is_parent_class(native_base, SNAME("Resource"))) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; variable->export_info.hint_string = class_name; @@ -3796,7 +3781,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node String enum_hint_string; bool first = true; - for (const KeyValue<StringName, int> &E : export_type.enum_values) { + for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) { if (!first) { enum_hint_string += ","; } else { @@ -4010,13 +3995,17 @@ String GDScriptParser::DataType::to_string() const { } return native_type.operator String(); } - case ENUM: - return enum_type.operator String() + " (enum)"; + case ENUM: { + // native_type contains either the native class defining the enum + // or the fully qualified class name of the script defining the enum + return String(native_type).get_file(); // Remove path, keep filename + } + case RESOLVING: case UNRESOLVED: return "<unresolved type>"; } - ERR_FAIL_V_MSG("<unresolved type", "Kind set outside the enum range."); + 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) { @@ -4698,9 +4687,9 @@ void GDScriptParser::TreePrinter::print_parameter(ParameterNode *p_parameter) { push_text(" : "); print_type(p_parameter->datatype_specifier); } - if (p_parameter->default_value != nullptr) { + if (p_parameter->initializer != nullptr) { push_text(" = "); - print_expression(p_parameter->default_value); + print_expression(p_parameter->initializer); } } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 1850a44678..74e12d0b5e 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_parser.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_parser.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_PARSER_H #define GDSCRIPT_PARSER_H @@ -57,6 +57,7 @@ public: struct AnnotationNode; struct ArrayNode; struct AssertNode; + struct AssignableNode; struct AssignmentNode; struct AwaitNode; struct BinaryOpNode; @@ -107,6 +108,7 @@ public: CLASS, // GDScript. ENUM, // Enumeration. VARIANT, // Can be any type. + RESOLVING, // Currently resolving. UNRESOLVED, }; Kind kind = UNRESOLVED; @@ -131,11 +133,12 @@ public: ClassNode *class_type = nullptr; MethodInfo method_info; // For callable/signals. - HashMap<StringName, int> enum_values; // For enums. + HashMap<StringName, int64_t> enum_values; // For enums. - _FORCE_INLINE_ bool is_set() const { return kind != UNRESOLVED; } + _FORCE_INLINE_ bool is_set() const { return kind != RESOLVING && kind != UNRESOLVED; } + _FORCE_INLINE_ bool is_resolving() const { return kind == RESOLVING; } _FORCE_INLINE_ bool has_no_type() const { return type_source == UNDETECTED; } - _FORCE_INLINE_ bool is_variant() const { return kind == VARIANT || kind == UNRESOLVED; } + _FORCE_INLINE_ bool is_variant() const { return kind == VARIANT || kind == RESOLVING || kind == UNRESOLVED; } _FORCE_INLINE_ bool is_hard_type() const { return type_source > INFERRED; } String to_string() const; @@ -182,12 +185,13 @@ public: case BUILTIN: return builtin_type == p_other.builtin_type; case NATIVE: - case ENUM: - return native_type == p_other.native_type && enum_type == p_other.enum_type; + case ENUM: // Enums use native_type to identify the enum and its base class. + return native_type == p_other.native_type; case SCRIPT: return script_type == p_other.script_type; case CLASS: return class_type == p_other.class_type; + case RESOLVING: case UNRESOLVED: break; } @@ -351,6 +355,20 @@ public: } }; + struct AssignableNode : public Node { + IdentifierNode *identifier = nullptr; + ExpressionNode *initializer = nullptr; + TypeNode *datatype_specifier = nullptr; + bool infer_datatype = false; + bool use_conversion_assign = false; + int usages = 0; + + virtual ~AssignableNode() {} + + protected: + AssignableNode() {} + }; + struct AssignmentNode : public ExpressionNode { // Assignment is not really an expression but it's easier to parse as if it were. enum Operation { @@ -469,7 +487,7 @@ public: EnumNode *parent_enum = nullptr; int index = -1; bool resolved = false; - int value = 0; + int64_t value = 0; int line = 0; int leftmost_column = 0; int rightmost_column = 0; @@ -480,6 +498,7 @@ public: IdentifierNode *identifier = nullptr; Vector<Value> values; + Variant dictionary; #ifdef TOOLS_ENABLED String doc_description; #endif // TOOLS_ENABLED @@ -516,6 +535,32 @@ public: }; EnumNode::Value enum_value; + String get_name() const { + switch (type) { + case UNDEFINED: + return "<undefined member>"; + case CLASS: + // All class-type members have an id. + return m_class->identifier->name; + case CONSTANT: + return constant->identifier->name; + case FUNCTION: + return function->identifier->name; + case SIGNAL: + return signal->identifier->name; + case VARIABLE: + return variable->identifier->name; + case ENUM: + // All enum-type members have an id. + return m_enum->identifier->name; + case ENUM_VALUE: + return enum_value.identifier->name; + case GROUP: + return annotation->export_info.name; + } + return ""; + } + String get_type_name() const { switch (type) { case UNDEFINED: @@ -576,31 +621,42 @@ public: return variable->get_datatype(); case ENUM: return m_enum->get_datatype(); - case ENUM_VALUE: { - // Always integer. - DataType out_type; - out_type.type_source = DataType::ANNOTATED_EXPLICIT; - out_type.kind = DataType::BUILTIN; - out_type.builtin_type = Variant::INT; - return out_type; - } - case SIGNAL: { - DataType out_type; - out_type.type_source = DataType::ANNOTATED_EXPLICIT; - out_type.kind = DataType::BUILTIN; - out_type.builtin_type = Variant::SIGNAL; - // TODO: Add parameter info. - return out_type; - } - case GROUP: { + case ENUM_VALUE: + return enum_value.identifier->get_datatype(); + case SIGNAL: + return signal->get_datatype(); + case GROUP: return DataType(); - } case UNDEFINED: return DataType(); } ERR_FAIL_V_MSG(DataType(), "Reaching unhandled type."); } + Node *get_source_node() const { + switch (type) { + case CLASS: + return m_class; + case CONSTANT: + return constant; + case FUNCTION: + return function; + case VARIABLE: + return variable; + case ENUM: + return m_enum; + case ENUM_VALUE: + return enum_value.identifier; + case SIGNAL: + return signal; + case GROUP: + return annotation; + case UNDEFINED: + return nullptr; + } + ERR_FAIL_V_MSG(nullptr, "Reaching unhandled type."); + } + Member() {} Member(ClassNode *p_class) { @@ -691,12 +747,7 @@ public: } }; - struct ConstantNode : public Node { - IdentifierNode *identifier = nullptr; - ExpressionNode *initializer = nullptr; - TypeNode *datatype_specifier = nullptr; - bool infer_datatype = false; - int usages = 0; + struct ConstantNode : public AssignableNode { #ifdef TOOLS_ENABLED String doc_description; #endif // TOOLS_ENABLED @@ -707,7 +758,6 @@ public: }; struct ContinueNode : public Node { - bool is_for_match = false; ContinueNode() { type = CONTINUE; } @@ -786,6 +836,7 @@ public: LOCAL_VARIABLE, LOCAL_ITERATOR, // `for` loop iterator. LOCAL_BIND, // Pattern bind. + MEMBER_SIGNAL, MEMBER_VARIABLE, MEMBER_CONSTANT, INHERITED_VARIABLE, @@ -860,13 +911,7 @@ public: } }; - struct ParameterNode : public Node { - IdentifierNode *identifier = nullptr; - ExpressionNode *default_value = nullptr; - TypeNode *datatype_specifier = nullptr; - bool infer_datatype = false; - int usages = 0; - + struct ParameterNode : public AssignableNode { ParameterNode() { type = PARAMETER; } @@ -1054,12 +1099,12 @@ public: HashMap<StringName, int> locals_indices; FunctionNode *parent_function = nullptr; - ForNode *parent_for = nullptr; IfNode *parent_if = nullptr; bool has_return = false; bool has_continue = false; bool has_unreachable_code = false; // Just so warnings aren't given more than once per block. + bool is_loop = false; bool has_local(const StringName &p_name) const; const Local &get_local(const StringName &p_name) const; @@ -1115,18 +1160,13 @@ public: } }; - struct VariableNode : public Node { + struct VariableNode : public AssignableNode { enum PropertyStyle { PROP_NONE, PROP_INLINE, PROP_SETGET, }; - IdentifierNode *identifier = nullptr; - ExpressionNode *initializer = nullptr; - TypeNode *datatype_specifier = nullptr; - bool infer_datatype = false; - PropertyStyle property = PROP_NONE; union { FunctionNode *setter = nullptr; @@ -1142,8 +1182,6 @@ public: bool onready = false; PropertyInfo export_info; int assignments = 0; - int usages = 0; - bool use_conversion_assign = false; #ifdef TOOLS_ENABLED String doc_description; #endif // TOOLS_ENABLED @@ -1215,14 +1253,14 @@ private: bool panic_mode = false; bool can_break = false; bool can_continue = false; - bool is_continue_match = false; // Whether a `continue` will act on a `match`. - bool is_ignoring_warnings = false; List<bool> multiline_stack; ClassNode *head = nullptr; Node *list = nullptr; List<ParserError> errors; + #ifdef DEBUG_ENABLED + bool is_ignoring_warnings = false; List<GDScriptWarning> warnings; HashSet<String> ignored_warnings; HashSet<uint32_t> ignored_warning_codes; @@ -1321,8 +1359,11 @@ private: void clear(); void push_error(const String &p_message, const Node *p_origin = nullptr); #ifdef DEBUG_ENABLED - void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const String &p_symbol1 = String(), const String &p_symbol2 = String(), const String &p_symbol3 = String(), const String &p_symbol4 = String()); void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Vector<String> &p_symbols); + template <typename... Symbols> + void push_warning(const Node *p_source, GDScriptWarning::Code p_code, const Symbols &...p_symbols) { + push_warning(p_source, p_code, Vector<String>{ p_symbols... }); + } #endif void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = false); @@ -1360,7 +1401,7 @@ private: // Annotations AnnotationNode *parse_annotation(uint32_t p_valid_targets); bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false); - bool validate_annotation_arguments(AnnotationNode *p_annotation); + bool validate_annotation_argument_count(AnnotationNode *p_annotation); void clear_unused_annotations(); bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target); bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target); @@ -1428,6 +1469,8 @@ public: Error parse(const String &p_source_code, const String &p_script_path, bool p_for_completion); ClassNode *get_tree() const { return head; } bool is_tool() const { return _is_tool; } + ClassNode *find_class(const String &p_qualified_name) const; + bool has_class(const GDScriptParser::ClassNode *p_class) const; static Variant::Type get_builtin_type(const StringName &p_type); CompletionContext get_completion_context() const { return completion_context; } diff --git a/modules/gdscript/gdscript_rpc_callable.cpp b/modules/gdscript/gdscript_rpc_callable.cpp index 4e12419357..a4dd8a8d3c 100644 --- a/modules/gdscript/gdscript_rpc_callable.cpp +++ b/modules/gdscript/gdscript_rpc_callable.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_rpc_callable.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_rpc_callable.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_rpc_callable.h" diff --git a/modules/gdscript/gdscript_rpc_callable.h b/modules/gdscript/gdscript_rpc_callable.h index 83b9c7e2df..c1007b18b0 100644 --- a/modules/gdscript/gdscript_rpc_callable.h +++ b/modules/gdscript/gdscript_rpc_callable.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_rpc_callable.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_rpc_callable.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_RPC_CALLABLE_H #define GDSCRIPT_RPC_CALLABLE_H diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 9bbfd7aece..d7f1114fd3 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -1,40 +1,44 @@ -/*************************************************************************/ -/* gdscript_tokenizer.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_tokenizer.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_tokenizer.h" #include "core/error/error_macros.h" +#include "core/string/char_utils.h" #ifdef TOOLS_ENABLED #include "editor/editor_settings.h" #endif +#ifdef DEBUG_ENABLED +#include "servers/text_server.h" +#endif static const char *token_names[] = { "Empty", // EMPTY, @@ -164,6 +168,7 @@ bool GDScriptTokenizer::Token::is_identifier() const { switch (type) { case IDENTIFIER: case MATCH: // Used in String.match(). + case CONST_INF: // Used in Vector{2,3,4}.INF return true; default: return false; @@ -434,10 +439,12 @@ GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(char32_t p_test, To } GDScriptTokenizer::Token GDScriptTokenizer::annotation() { - if (!is_ascii_identifier_char(_peek())) { + if (is_unicode_identifier_start(_peek())) { + _advance(); // Consume start character. + } else { push_error("Expected annotation identifier after \"@\"."); } - while (is_ascii_identifier_char(_peek())) { + while (is_unicode_identifier_continue(_peek())) { // Consume all identifier characters. _advance(); } @@ -446,7 +453,6 @@ GDScriptTokenizer::Token GDScriptTokenizer::annotation() { return annotation; } -GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() { #define KEYWORDS(KEYWORD_GROUP, KEYWORD) \ KEYWORD_GROUP('a') \ KEYWORD("as", Token::AS) \ @@ -511,8 +517,21 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() { #define MIN_KEYWORD_LENGTH 2 #define MAX_KEYWORD_LENGTH 10 - // Consume all alphanumeric characters. - while (is_ascii_identifier_char(_peek())) { +#ifdef DEBUG_ENABLED +void GDScriptTokenizer::make_keyword_list() { +#define KEYWORD_LINE(keyword, token_type) keyword, +#define KEYWORD_GROUP_IGNORE(group) + keyword_list = { + KEYWORDS(KEYWORD_GROUP_IGNORE, KEYWORD_LINE) + }; +#undef KEYWORD_LINE +#undef KEYWORD_GROUP_IGNORE +} +#endif // DEBUG_ENABLED + +GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() { + // Consume all identifier characters. + while (is_unicode_identifier_continue(_peek())) { _advance(); } @@ -564,15 +583,28 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() { } // Not a keyword, so must be an identifier. - return make_identifier(name); + Token id = make_identifier(name); + +#ifdef DEBUG_ENABLED + // Additional checks for identifiers but only in debug and if it's available in TextServer. + if (TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY)) { + int64_t confusable = TS->is_confusable(name, keyword_list); + if (confusable >= 0) { + push_error(vformat(R"(Identifier "%s" is visually similar to the GDScript keyword "%s" and thus not allowed.)", name, keyword_list[confusable])); + } + } +#endif // DEBUG_ENABLED + + return id; -#undef KEYWORDS -#undef MIN_KEYWORD_LENGTH -#undef MAX_KEYWORD_LENGTH #undef KEYWORD_GROUP_CASE #undef KEYWORD } +#undef MAX_KEYWORD_LENGTH +#undef MIN_KEYWORD_LENGTH +#undef KEYWORDS + void GDScriptTokenizer::newline(bool p_make_token) { // Don't overwrite previous newline, nor create if we want a line continuation. if (p_make_token && !pending_newline && !line_continuation) { @@ -719,7 +751,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() { error.rightmost_column = column + 1; push_error(error); has_error = true; - } else if (is_ascii_identifier_char(_peek())) { + } else if (is_unicode_identifier_start(_peek()) || is_unicode_identifier_continue(_peek())) { // Letter at the end of the number. push_error("Invalid numeric notation."); } @@ -1310,7 +1342,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { if (is_digit(c)) { return number(); - } else if (is_ascii_identifier_char(c)) { + } else if (is_unicode_identifier_start(c)) { return potential_identifier(); } @@ -1503,7 +1535,11 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() { } default: - return make_error(vformat(R"(Unknown character "%s".)", String(&c, 1))); + if (is_whitespace(c)) { + return make_error(vformat(R"(Invalid white space character "\\u%X".)", static_cast<int32_t>(c))); + } else { + return make_error(vformat(R"(Unknown character "%s".)", String(&c, 1))); + } } } @@ -1513,4 +1549,7 @@ GDScriptTokenizer::GDScriptTokenizer() { tab_size = EditorSettings::get_singleton()->get_setting("text_editor/behavior/indent/size"); } #endif // TOOLS_ENABLED +#ifdef DEBUG_ENABLED + make_keyword_list(); +#endif // DEBUG_ENABLED } diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h index 68b2c6eb1c..608840d3f1 100644 --- a/modules/gdscript/gdscript_tokenizer.h +++ b/modules/gdscript/gdscript_tokenizer.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_tokenizer.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_tokenizer.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_TOKENIZER_H #define GDSCRIPT_TOKENIZER_H @@ -224,6 +224,9 @@ private: char32_t indent_char = '\0'; int position = 0; int length = 0; +#ifdef DEBUG_ENABLED + Vector<String> keyword_list; +#endif // DEBUG_ENABLED #ifdef TOOLS_ENABLED HashMap<int, CommentData> comments; @@ -239,6 +242,10 @@ private: void _skip_whitespace(); void check_indent(); +#ifdef DEBUG_ENABLED + void make_keyword_list(); +#endif // DEBUG_ENABLED + Token make_error(const String &p_message); void push_error(const String &p_message); void push_error(const Token &p_error); diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index bcbe8b8d2b..10d83dcfe5 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_utility_functions.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_utility_functions.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_utility_functions.h" @@ -294,6 +294,7 @@ struct GDScriptUtilityFunctionsDefinitions { } GDScript *p = base.ptr(); + String path = p->get_script_path(); Vector<StringName> sname; while (p->_owner) { @@ -302,7 +303,7 @@ struct GDScriptUtilityFunctionsDefinitions { } sname.reverse(); - if (!p->path.is_resource_file()) { + if (!path.is_resource_file()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::DICTIONARY; @@ -317,7 +318,7 @@ struct GDScriptUtilityFunctionsDefinitions { Dictionary d; d["@subpath"] = cp; - d["@path"] = p->get_path(); + d["@path"] = path; for (const KeyValue<StringName, GDScript::MemberInfo> &E : base->member_indices) { if (!d.has(E.key)) { diff --git a/modules/gdscript/gdscript_utility_functions.h b/modules/gdscript/gdscript_utility_functions.h index 0f07db857f..40e9379a3a 100644 --- a/modules/gdscript/gdscript_utility_functions.h +++ b/modules/gdscript/gdscript_utility_functions.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_utility_functions.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_utility_functions.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_UTILITY_FUNCTIONS_H #define GDSCRIPT_UTILITY_FUNCTIONS_H diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index c73ba798aa..4ea4438b5e 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_vm.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_vm.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_function.h" @@ -35,39 +35,6 @@ #include "gdscript.h" #include "gdscript_lambda_callable.h" -Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_instance, Variant *p_stack, String &r_error) const { - int address = p_address & ADDR_MASK; - - //sequential table (jump table generated by compiler) - switch ((p_address & ADDR_TYPE_MASK) >> ADDR_BITS) { - case ADDR_TYPE_STACK: { -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, _stack_size, nullptr); -#endif - return &p_stack[address]; - } break; - case ADDR_TYPE_CONSTANT: { -#ifdef DEBUG_ENABLED - ERR_FAIL_INDEX_V(address, _constant_count, nullptr); -#endif - return &_constants_ptr[address]; - } break; - case ADDR_TYPE_MEMBER: { -#ifdef DEBUG_ENABLED - if (unlikely(!p_instance)) { - r_error = "Cannot access member without instance."; - return nullptr; - } -#endif - //member indexing is O(1) - return &p_instance->members.write[address]; - } break; - } - - ERR_FAIL_V_MSG(nullptr, "Bad code! (unknown addressing mode)."); - return nullptr; -} - #ifdef DEBUG_ENABLED static String _get_script_name(const Ref<Script> p_script) { Ref<GDScript> gdscript = p_script; @@ -413,14 +380,19 @@ void (*type_init_function_table[])(Variant *) = { #define OPCODE(m_op) \ m_op: -#define OPCODE_WHILE(m_test) \ - OPSWHILE: +#define OPCODE_WHILE(m_test) #define OPCODES_END \ OPSEXIT: #define OPCODES_OUT \ OPSOUT: -#define DISPATCH_OPCODE goto OPSWHILE #define OPCODE_SWITCH(m_test) goto *switch_table_ops[m_test]; +#ifdef DEBUG_ENABLED +#define DISPATCH_OPCODE \ + last_opcode = _code_ptr[ip]; \ + goto *switch_table_ops[last_opcode] +#else +#define DISPATCH_OPCODE goto *switch_table_ops[_code_ptr[ip]] +#endif #define OPCODE_BREAK goto OPSEXIT #define OPCODE_OUT goto OPSOUT #else @@ -607,21 +579,52 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #define CHECK_SPACE(m_space) \ GD_ERR_BREAK((ip + m_space) > _code_size) -#define GET_VARIANT_PTR(m_v, m_code_ofs) \ - Variant *m_v; \ - m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, stack, err_text); \ - if (unlikely(!m_v)) \ - OPCODE_BREAK; +#define GET_VARIANT_PTR(m_v, m_code_ofs) \ + Variant *m_v; \ + { \ + int address = _code_ptr[ip + 1 + (m_code_ofs)]; \ + int address_type = (address & ADDR_TYPE_MASK) >> ADDR_BITS; \ + if (unlikely(address_type < 0 || address_type >= ADDR_TYPE_MAX)) { \ + err_text = "Bad address type."; \ + OPCODE_BREAK; \ + } \ + int address_index = address & ADDR_MASK; \ + if (unlikely(address_index < 0 || address_index >= variant_address_limits[address_type])) { \ + if (address_type == ADDR_TYPE_MEMBER && !p_instance) { \ + err_text = "Cannot access member without instance."; \ + } else { \ + err_text = "Bad address index."; \ + } \ + OPCODE_BREAK; \ + } \ + m_v = &variant_addresses[address_type][address_index]; \ + if (unlikely(!m_v)) \ + OPCODE_BREAK; \ + } #else #define GD_ERR_BREAK(m_cond) #define CHECK_SPACE(m_space) -#define GET_VARIANT_PTR(m_v, m_code_ofs) \ - Variant *m_v; \ - m_v = _get_variant(_code_ptr[ip + m_code_ofs], p_instance, stack, err_text); + +#define GET_VARIANT_PTR(m_v, m_code_ofs) \ + Variant *m_v; \ + { \ + int address = _code_ptr[ip + 1 + (m_code_ofs)]; \ + m_v = &variant_addresses[(address & ADDR_TYPE_MASK) >> ADDR_BITS][address & ADDR_MASK]; \ + if (unlikely(!m_v)) \ + OPCODE_BREAK; \ + } #endif +#define LOAD_INSTRUCTION_ARGS \ + int instr_arg_count = _code_ptr[ip + 1]; \ + for (int i = 0; i < instr_arg_count; i++) { \ + GET_VARIANT_PTR(v, i + 1); \ + instruction_args[i] = v; \ + } \ + ip += 1; // Offset to skip instruction argcount. + #define GET_INSTRUCTION_ARG(m_v, m_idx) \ Variant *m_v = instruction_args[m_idx] @@ -639,21 +642,20 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a bool exit_ok = false; bool awaited = false; #endif +#ifdef DEBUG_ENABLED + int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0 }; +#endif + + Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr }; #ifdef DEBUG_ENABLED OPCODE_WHILE(ip < _code_size) { - int last_opcode = _code_ptr[ip] & INSTR_MASK; + int last_opcode = _code_ptr[ip]; #else OPCODE_WHILE(true) { #endif - // Load arguments for the instruction before each instruction. - int instr_arg_count = ((_code_ptr[ip]) & INSTR_ARGS_MASK) >> INSTR_BITS; - for (int i = 0; i < instr_arg_count; i++) { - GET_VARIANT_PTR(v, i + 1); - instruction_args[i] = v; - } - OPCODE_SWITCH(_code_ptr[ip] & INSTR_MASK) { + OPCODE_SWITCH(_code_ptr[ip]) { OPCODE(OPCODE_OPERATOR) { CHECK_SPACE(5); @@ -661,9 +663,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a Variant::Operator op = (Variant::Operator)_code_ptr[ip + 4]; GD_ERR_BREAK(op >= Variant::OP_MAX); - GET_INSTRUCTION_ARG(a, 0); - GET_INSTRUCTION_ARG(b, 1); - GET_INSTRUCTION_ARG(dst, 2); + GET_VARIANT_PTR(a, 0); + GET_VARIANT_PTR(b, 1); + GET_VARIANT_PTR(dst, 2); #ifdef DEBUG_ENABLED @@ -696,9 +698,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(operator_idx < 0 || operator_idx >= _operator_funcs_count); Variant::ValidatedOperatorEvaluator operator_func = _operator_funcs_ptr[operator_idx]; - GET_INSTRUCTION_ARG(a, 0); - GET_INSTRUCTION_ARG(b, 1); - GET_INSTRUCTION_ARG(dst, 2); + GET_VARIANT_PTR(a, 0); + GET_VARIANT_PTR(b, 1); + GET_VARIANT_PTR(dst, 2); operator_func(a, b, dst); @@ -709,9 +711,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_EXTENDS_TEST) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(a, 0); - GET_INSTRUCTION_ARG(b, 1); - GET_INSTRUCTION_ARG(dst, 2); + GET_VARIANT_PTR(a, 0); + GET_VARIANT_PTR(b, 1); + GET_VARIANT_PTR(dst, 2); #ifdef DEBUG_ENABLED if (b->get_type() != Variant::OBJECT || b->operator Object *() == nullptr) { @@ -784,8 +786,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_IS_BUILTIN) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(value, 0); - GET_INSTRUCTION_ARG(dst, 1); + GET_VARIANT_PTR(value, 0); + GET_VARIANT_PTR(dst, 1); Variant::Type var_type = (Variant::Type)_code_ptr[ip + 3]; GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX); @@ -798,9 +800,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_SET_KEYED) { CHECK_SPACE(3); - GET_INSTRUCTION_ARG(dst, 0); - GET_INSTRUCTION_ARG(index, 1); - GET_INSTRUCTION_ARG(value, 2); + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(index, 1); + GET_VARIANT_PTR(value, 2); bool valid; dst->set(*index, *value, &valid); @@ -824,9 +826,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_SET_KEYED_VALIDATED) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(dst, 0); - GET_INSTRUCTION_ARG(index, 1); - GET_INSTRUCTION_ARG(value, 2); + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(index, 1); + GET_VARIANT_PTR(value, 2); int index_setter = _code_ptr[ip + 4]; GD_ERR_BREAK(index_setter < 0 || index_setter >= _keyed_setters_count); @@ -854,9 +856,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_SET_INDEXED_VALIDATED) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(dst, 0); - GET_INSTRUCTION_ARG(index, 1); - GET_INSTRUCTION_ARG(value, 2); + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(index, 1); + GET_VARIANT_PTR(value, 2); int index_setter = _code_ptr[ip + 4]; GD_ERR_BREAK(index_setter < 0 || index_setter >= _indexed_setters_count); @@ -886,9 +888,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_GET_KEYED) { CHECK_SPACE(3); - GET_INSTRUCTION_ARG(src, 0); - GET_INSTRUCTION_ARG(index, 1); - GET_INSTRUCTION_ARG(dst, 2); + GET_VARIANT_PTR(src, 0); + GET_VARIANT_PTR(index, 1); + GET_VARIANT_PTR(dst, 2); bool valid; #ifdef DEBUG_ENABLED @@ -918,9 +920,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_GET_KEYED_VALIDATED) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(src, 0); - GET_INSTRUCTION_ARG(key, 1); - GET_INSTRUCTION_ARG(dst, 2); + GET_VARIANT_PTR(src, 0); + GET_VARIANT_PTR(key, 1); + GET_VARIANT_PTR(dst, 2); int index_getter = _code_ptr[ip + 4]; GD_ERR_BREAK(index_getter < 0 || index_getter >= _keyed_getters_count); @@ -954,9 +956,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_GET_INDEXED_VALIDATED) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(src, 0); - GET_INSTRUCTION_ARG(index, 1); - GET_INSTRUCTION_ARG(dst, 2); + GET_VARIANT_PTR(src, 0); + GET_VARIANT_PTR(index, 1); + GET_VARIANT_PTR(dst, 2); int index_getter = _code_ptr[ip + 4]; GD_ERR_BREAK(index_getter < 0 || index_getter >= _indexed_getters_count); @@ -986,8 +988,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_SET_NAMED) { CHECK_SPACE(3); - GET_INSTRUCTION_ARG(dst, 0); - GET_INSTRUCTION_ARG(value, 1); + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(value, 1); int indexname = _code_ptr[ip + 3]; @@ -1011,8 +1013,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_SET_NAMED_VALIDATED) { CHECK_SPACE(3); - GET_INSTRUCTION_ARG(dst, 0); - GET_INSTRUCTION_ARG(value, 1); + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(value, 1); int index_setter = _code_ptr[ip + 3]; GD_ERR_BREAK(index_setter < 0 || index_setter >= _setters_count); @@ -1026,8 +1028,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_GET_NAMED) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(src, 0); - GET_INSTRUCTION_ARG(dst, 1); + GET_VARIANT_PTR(src, 0); + GET_VARIANT_PTR(dst, 1); int indexname = _code_ptr[ip + 3]; @@ -1056,8 +1058,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_GET_NAMED_VALIDATED) { CHECK_SPACE(3); - GET_INSTRUCTION_ARG(src, 0); - GET_INSTRUCTION_ARG(dst, 1); + GET_VARIANT_PTR(src, 0); + GET_VARIANT_PTR(dst, 1); int index_getter = _code_ptr[ip + 3]; GD_ERR_BREAK(index_getter < 0 || index_getter >= _getters_count); @@ -1070,7 +1072,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_SET_MEMBER) { CHECK_SPACE(3); - GET_INSTRUCTION_ARG(src, 0); + GET_VARIANT_PTR(src, 0); int indexname = _code_ptr[ip + 2]; GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); const StringName *index = &_global_names_ptr[indexname]; @@ -1094,7 +1096,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_GET_MEMBER) { CHECK_SPACE(3); - GET_INSTRUCTION_ARG(dst, 0); + GET_VARIANT_PTR(dst, 0); int indexname = _code_ptr[ip + 2]; GD_ERR_BREAK(indexname < 0 || indexname >= _global_names_count); const StringName *index = &_global_names_ptr[indexname]; @@ -1113,8 +1115,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ASSIGN) { CHECK_SPACE(3); - GET_INSTRUCTION_ARG(dst, 0); - GET_INSTRUCTION_ARG(src, 1); + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(src, 1); *dst = *src; @@ -1124,7 +1126,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ASSIGN_TRUE) { CHECK_SPACE(2); - GET_INSTRUCTION_ARG(dst, 0); + GET_VARIANT_PTR(dst, 0); *dst = true; @@ -1134,7 +1136,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ASSIGN_FALSE) { CHECK_SPACE(2); - GET_INSTRUCTION_ARG(dst, 0); + GET_VARIANT_PTR(dst, 0); *dst = false; @@ -1144,8 +1146,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ASSIGN_TYPED_BUILTIN) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(dst, 0); - GET_INSTRUCTION_ARG(src, 1); + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(src, 1); Variant::Type var_type = (Variant::Type)_code_ptr[ip + 3]; GD_ERR_BREAK(var_type < 0 || var_type >= Variant::VARIANT_MAX); @@ -1173,8 +1175,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ASSIGN_TYPED_ARRAY) { CHECK_SPACE(3); - GET_INSTRUCTION_ARG(dst, 0); - GET_INSTRUCTION_ARG(src, 1); + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(src, 1); Array *dst_arr = VariantInternal::get_array(dst); @@ -1198,11 +1200,11 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(dst, 0); - GET_INSTRUCTION_ARG(src, 1); + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(src, 1); #ifdef DEBUG_ENABLED - GET_INSTRUCTION_ARG(type, 2); + GET_VARIANT_PTR(type, 2); GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(type->operator Object *()); GD_ERR_BREAK(!nc); if (src->get_type() != Variant::OBJECT && src->get_type() != Variant::NIL) { @@ -1226,11 +1228,11 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ASSIGN_TYPED_SCRIPT) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(dst, 0); - GET_INSTRUCTION_ARG(src, 1); + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(src, 1); #ifdef DEBUG_ENABLED - GET_INSTRUCTION_ARG(type, 2); + GET_VARIANT_PTR(type, 2); Script *base_type = Object::cast_to<Script>(type->operator Object *()); GD_ERR_BREAK(!base_type); @@ -1275,8 +1277,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_CAST_TO_BUILTIN) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(src, 0); - GET_INSTRUCTION_ARG(dst, 1); + GET_VARIANT_PTR(src, 0); + GET_VARIANT_PTR(dst, 1); Variant::Type to_type = (Variant::Type)_code_ptr[ip + 3]; GD_ERR_BREAK(to_type < 0 || to_type >= Variant::VARIANT_MAX); @@ -1304,9 +1306,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_CAST_TO_NATIVE) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(src, 0); - GET_INSTRUCTION_ARG(dst, 1); - GET_INSTRUCTION_ARG(to_type, 2); + GET_VARIANT_PTR(src, 0); + GET_VARIANT_PTR(dst, 1); + GET_VARIANT_PTR(to_type, 2); GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(to_type->operator Object *()); GD_ERR_BREAK(!nc); @@ -1335,9 +1337,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_CAST_TO_SCRIPT) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(src, 0); - GET_INSTRUCTION_ARG(dst, 1); - GET_INSTRUCTION_ARG(to_type, 2); + GET_VARIANT_PTR(src, 0); + GET_VARIANT_PTR(dst, 1); + GET_VARIANT_PTR(to_type, 2); Script *base_type = Object::cast_to<Script>(to_type->operator Object *()); @@ -1383,6 +1385,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_CONSTRUCT) { + LOAD_INSTRUCTION_ARGS CHECK_SPACE(2 + instr_arg_count); ip += instr_arg_count; @@ -1390,6 +1393,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a int argc = _code_ptr[ip + 1]; Variant::Type t = Variant::Type(_code_ptr[ip + 2]); + Variant **argptrs = instruction_args; GET_INSTRUCTION_ARG(dst, argc); @@ -1409,8 +1413,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_CONSTRUCT_VALIDATED) { + LOAD_INSTRUCTION_ARGS CHECK_SPACE(2 + instr_arg_count); - ip += instr_arg_count; int argc = _code_ptr[ip + 1]; @@ -1430,6 +1434,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_CONSTRUCT_ARRAY) { + LOAD_INSTRUCTION_ARGS CHECK_SPACE(1 + instr_arg_count); ip += instr_arg_count; @@ -1451,6 +1456,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_CONSTRUCT_TYPED_ARRAY) { + LOAD_INSTRUCTION_ARGS CHECK_SPACE(3 + instr_arg_count); ip += instr_arg_count; @@ -1480,6 +1486,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_CONSTRUCT_DICTIONARY) { + LOAD_INSTRUCTION_ARGS CHECK_SPACE(2 + instr_arg_count); ip += instr_arg_count; @@ -1504,11 +1511,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_CALL_ASYNC) OPCODE(OPCODE_CALL_RETURN) OPCODE(OPCODE_CALL) { - CHECK_SPACE(3 + instr_arg_count); - bool call_ret = (_code_ptr[ip] & INSTR_MASK) != OPCODE_CALL; + bool call_ret = (_code_ptr[ip]) != OPCODE_CALL; #ifdef DEBUG_ENABLED - bool call_async = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_ASYNC; + bool call_async = (_code_ptr[ip]) == OPCODE_CALL_ASYNC; #endif + LOAD_INSTRUCTION_ARGS + CHECK_SPACE(3 + instr_arg_count); ip += instr_arg_count; @@ -1533,8 +1541,28 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a Callable::CallError err; if (call_ret) { GET_INSTRUCTION_ARG(ret, argc + 1); +#ifdef DEBUG_ENABLED + Variant::Type base_type = base->get_type(); + Object *base_obj = base->get_validated_object(); + StringName base_class = base_obj ? base_obj->get_class_name() : StringName(); +#endif base->callp(*methodname, (const Variant **)argptrs, argc, *ret, err); #ifdef DEBUG_ENABLED + if (ret->get_type() == Variant::NIL) { + if (base_type == Variant::OBJECT) { + if (base_obj) { + MethodBind *method = ClassDB::get_method(base_class, *methodname); + if (*methodname == CoreStringNames::get_singleton()->_free || (method && !method->has_return())) { + err_text = R"(Trying to get a return value of a method that returns "void")"; + OPCODE_BREAK; + } + } + } else if (Variant::has_builtin_method(base_type, *methodname) && !Variant::has_builtin_method_return_value(base_type, *methodname)) { + err_text = R"(Trying to get a return value of a method that returns "void")"; + OPCODE_BREAK; + } + } + if (!call_async && ret->get_type() == Variant::OBJECT) { // Check if getting a function state without await. bool was_freed = false; @@ -1603,8 +1631,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_CALL_METHOD_BIND) OPCODE(OPCODE_CALL_METHOD_BIND_RET) { + bool call_ret = (_code_ptr[ip]) == OPCODE_CALL_METHOD_BIND_RET; + LOAD_INSTRUCTION_ARGS CHECK_SPACE(3 + instr_arg_count); - bool call_ret = (_code_ptr[ip] & INSTR_MASK) == OPCODE_CALL_METHOD_BIND_RET; ip += instr_arg_count; @@ -1682,6 +1711,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_CALL_BUILTIN_STATIC) { + LOAD_INSTRUCTION_ARGS CHECK_SPACE(4 + instr_arg_count); ip += instr_arg_count; @@ -1727,6 +1757,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_CALL_NATIVE_STATIC) { + LOAD_INSTRUCTION_ARGS CHECK_SPACE(3 + instr_arg_count); ip += instr_arg_count; @@ -1770,6 +1801,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED #define OPCODE_CALL_PTR(m_type) \ OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \ + LOAD_INSTRUCTION_ARGS \ CHECK_SPACE(3 + instr_arg_count); \ ip += instr_arg_count; \ int argc = _code_ptr[ip + 1]; \ @@ -1808,6 +1840,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #else #define OPCODE_CALL_PTR(m_type) \ OPCODE(OPCODE_CALL_PTRCALL_##m_type) { \ + LOAD_INSTRUCTION_ARGS \ CHECK_SPACE(3 + instr_arg_count); \ ip += instr_arg_count; \ int argc = _code_ptr[ip + 1]; \ @@ -1865,6 +1898,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE_CALL_PTR(PACKED_VECTOR3_ARRAY); OPCODE_CALL_PTR(PACKED_COLOR_ARRAY); OPCODE(OPCODE_CALL_PTRCALL_OBJECT) { + LOAD_INSTRUCTION_ARGS CHECK_SPACE(3 + instr_arg_count); ip += instr_arg_count; @@ -1919,6 +1953,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; OPCODE(OPCODE_CALL_PTRCALL_NO_RETURN) { + LOAD_INSTRUCTION_ARGS CHECK_SPACE(3 + instr_arg_count); ip += instr_arg_count; @@ -1971,6 +2006,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_CALL_BUILTIN_TYPE_VALIDATED) { + LOAD_INSTRUCTION_ARGS + CHECK_SPACE(3 + instr_arg_count); ip += instr_arg_count; @@ -2005,6 +2042,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_CALL_UTILITY) { + LOAD_INSTRUCTION_ARGS CHECK_SPACE(3 + instr_arg_count); ip += instr_arg_count; @@ -2039,6 +2077,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_CALL_UTILITY_VALIDATED) { + LOAD_INSTRUCTION_ARGS CHECK_SPACE(3 + instr_arg_count); ip += instr_arg_count; @@ -2060,6 +2099,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_CALL_GDSCRIPT_UTILITY) { + LOAD_INSTRUCTION_ARGS CHECK_SPACE(3 + instr_arg_count); ip += instr_arg_count; @@ -2095,6 +2135,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_CALL_SELF_BASE) { + LOAD_INSTRUCTION_ARGS CHECK_SPACE(3 + instr_arg_count); ip += instr_arg_count; @@ -2164,7 +2205,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a CHECK_SPACE(2); // Do the one-shot connect. - GET_INSTRUCTION_ARG(argobj, 0); + GET_VARIANT_PTR(argobj, 0); Signal sig; bool is_signal = true; @@ -2191,7 +2232,7 @@ 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); + GET_VARIANT_PTR(target, 2); *target = result; ip += 4; // Skip OPCODE_AWAIT_RESUME and its data. is_signal = false; @@ -2227,7 +2268,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } #ifdef DEBUG_ENABLED gdfs->state.function_name = name; - gdfs->state.script_path = _script->get_path(); + gdfs->state.script_path = _script->get_script_path(); #endif gdfs->state.defarg = defarg; gdfs->function = this; @@ -2257,13 +2298,14 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE_BREAK; } #endif - GET_INSTRUCTION_ARG(result, 0); + GET_VARIANT_PTR(result, 0); *result = p_state->result; ip += 2; } DISPATCH_OPCODE; OPCODE(OPCODE_CREATE_LAMBDA) { + LOAD_INSTRUCTION_ARGS CHECK_SPACE(2 + instr_arg_count); ip += instr_arg_count; @@ -2292,6 +2334,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a DISPATCH_OPCODE; OPCODE(OPCODE_CREATE_SELF_LAMBDA) { + LOAD_INSTRUCTION_ARGS CHECK_SPACE(2 + instr_arg_count); GD_ERR_BREAK(p_instance == nullptr); @@ -2338,7 +2381,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_JUMP_IF) { CHECK_SPACE(3); - GET_INSTRUCTION_ARG(test, 0); + GET_VARIANT_PTR(test, 0); bool result = test->booleanize(); @@ -2355,7 +2398,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_JUMP_IF_NOT) { CHECK_SPACE(3); - GET_INSTRUCTION_ARG(test, 0); + GET_VARIANT_PTR(test, 0); bool result = test->booleanize(); @@ -2378,7 +2421,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_JUMP_IF_SHARED) { CHECK_SPACE(3); - GET_INSTRUCTION_ARG(val, 0); + GET_VARIANT_PTR(val, 0); if (val->is_shared()) { int to = _code_ptr[ip + 2]; @@ -2392,7 +2435,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_RETURN) { CHECK_SPACE(2); - GET_INSTRUCTION_ARG(r, 0); + GET_VARIANT_PTR(r, 0); retvalue = *r; #ifdef DEBUG_ENABLED exit_ok = true; @@ -2402,7 +2445,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_RETURN_TYPED_BUILTIN) { CHECK_SPACE(3); - GET_INSTRUCTION_ARG(r, 0); + GET_VARIANT_PTR(r, 0); Variant::Type ret_type = (Variant::Type)_code_ptr[ip + 2]; GD_ERR_BREAK(ret_type < 0 || ret_type >= Variant::VARIANT_MAX); @@ -2433,9 +2476,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_RETURN_TYPED_ARRAY) { CHECK_SPACE(5); - GET_INSTRUCTION_ARG(r, 0); + GET_VARIANT_PTR(r, 0); - GET_INSTRUCTION_ARG(script_type, 1); + GET_VARIANT_PTR(script_type, 1); Variant::Type builtin_type = (Variant::Type)_code_ptr[ip + 3]; int native_type_idx = _code_ptr[ip + 4]; GD_ERR_BREAK(native_type_idx < 0 || native_type_idx >= _global_names_count); @@ -2474,9 +2517,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_RETURN_TYPED_NATIVE) { CHECK_SPACE(3); - GET_INSTRUCTION_ARG(r, 0); + GET_VARIANT_PTR(r, 0); - GET_INSTRUCTION_ARG(type, 1); + GET_VARIANT_PTR(type, 1); GDScriptNativeClass *nc = Object::cast_to<GDScriptNativeClass>(type->operator Object *()); GD_ERR_BREAK(!nc); @@ -2514,9 +2557,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_RETURN_TYPED_SCRIPT) { CHECK_SPACE(3); - GET_INSTRUCTION_ARG(r, 0); + GET_VARIANT_PTR(r, 0); - GET_INSTRUCTION_ARG(type, 1); + GET_VARIANT_PTR(type, 1); Script *base_type = Object::cast_to<Script>(type->operator Object *()); GD_ERR_BREAK(!base_type); @@ -2580,8 +2623,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_BEGIN) { CHECK_SPACE(8); // Space for this and a regular iterate. - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); bool valid; if (!container->iter_init(*counter, valid)) { @@ -2595,7 +2638,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); ip = jumpto; } else { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); *iterator = container->iter_get(*counter, valid); #ifdef DEBUG_ENABLED @@ -2612,8 +2655,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_BEGIN_INT) { CHECK_SPACE(8); // Check space for iterate instruction too. - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); int64_t size = *VariantInternal::get_int(container); @@ -2621,7 +2664,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a *VariantInternal::get_int(counter) = 0; if (size > 0) { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); VariantInternal::initialize(iterator, Variant::INT); *VariantInternal::get_int(iterator) = 0; @@ -2639,8 +2682,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_BEGIN_FLOAT) { CHECK_SPACE(8); // Check space for iterate instruction too. - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); double size = *VariantInternal::get_float(container); @@ -2648,7 +2691,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a *VariantInternal::get_float(counter) = 0.0; if (size > 0) { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); VariantInternal::initialize(iterator, Variant::FLOAT); *VariantInternal::get_float(iterator) = 0; @@ -2666,8 +2709,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_BEGIN_VECTOR2) { CHECK_SPACE(8); // Check space for iterate instruction too. - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); Vector2 *bounds = VariantInternal::get_vector2(container); @@ -2675,7 +2718,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a *VariantInternal::get_float(counter) = bounds->x; if (bounds->x < bounds->y) { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); VariantInternal::initialize(iterator, Variant::FLOAT); *VariantInternal::get_float(iterator) = bounds->x; @@ -2693,8 +2736,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_BEGIN_VECTOR2I) { CHECK_SPACE(8); // Check space for iterate instruction too. - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); Vector2i *bounds = VariantInternal::get_vector2i(container); @@ -2702,7 +2745,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a *VariantInternal::get_int(counter) = bounds->x; if (bounds->x < bounds->y) { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); VariantInternal::initialize(iterator, Variant::INT); *VariantInternal::get_int(iterator) = bounds->x; @@ -2720,8 +2763,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_BEGIN_VECTOR3) { CHECK_SPACE(8); // Check space for iterate instruction too. - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); Vector3 *bounds = VariantInternal::get_vector3(container); double from = bounds->x; @@ -2734,7 +2777,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a bool do_continue = from == to ? false : (from < to ? step > 0 : step < 0); if (do_continue) { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); VariantInternal::initialize(iterator, Variant::FLOAT); *VariantInternal::get_float(iterator) = from; @@ -2752,8 +2795,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_BEGIN_VECTOR3I) { CHECK_SPACE(8); // Check space for iterate instruction too. - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); Vector3i *bounds = VariantInternal::get_vector3i(container); int64_t from = bounds->x; @@ -2766,7 +2809,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a bool do_continue = from == to ? false : (from < to ? step > 0 : step < 0); if (do_continue) { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); VariantInternal::initialize(iterator, Variant::INT); *VariantInternal::get_int(iterator) = from; @@ -2784,8 +2827,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_BEGIN_STRING) { CHECK_SPACE(8); // Check space for iterate instruction too. - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); String *str = VariantInternal::get_string(container); @@ -2793,7 +2836,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a *VariantInternal::get_int(counter) = 0; if (!str->is_empty()) { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); VariantInternal::initialize(iterator, Variant::STRING); *VariantInternal::get_string(iterator) = str->substr(0, 1); @@ -2811,14 +2854,14 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_BEGIN_DICTIONARY) { CHECK_SPACE(8); // Check space for iterate instruction too. - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); Dictionary *dict = VariantInternal::get_dictionary(container); const Variant *next = dict->next(nullptr); if (!dict->is_empty()) { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); *counter = *next; *iterator = *next; @@ -2836,8 +2879,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_BEGIN_ARRAY) { CHECK_SPACE(8); // Check space for iterate instruction too. - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); Array *array = VariantInternal::get_array(container); @@ -2845,7 +2888,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a *VariantInternal::get_int(counter) = 0; if (!array->is_empty()) { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); *iterator = array->get(0); // Skip regular iterate. @@ -2862,13 +2905,13 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #define OPCODE_ITERATE_BEGIN_PACKED_ARRAY(m_var_type, m_elem_type, m_get_func, m_var_ret_type, m_ret_type, m_ret_get_func) \ OPCODE(OPCODE_ITERATE_BEGIN_PACKED_##m_var_type##_ARRAY) { \ CHECK_SPACE(8); \ - GET_INSTRUCTION_ARG(counter, 0); \ - GET_INSTRUCTION_ARG(container, 1); \ + GET_VARIANT_PTR(counter, 0); \ + GET_VARIANT_PTR(container, 1); \ Vector<m_elem_type> *array = VariantInternal::m_get_func(container); \ VariantInternal::initialize(counter, Variant::INT); \ *VariantInternal::get_int(counter) = 0; \ if (!array->is_empty()) { \ - GET_INSTRUCTION_ARG(iterator, 2); \ + GET_VARIANT_PTR(iterator, 2); \ VariantInternal::initialize(iterator, Variant::m_var_ret_type); \ m_ret_type *it = VariantInternal::m_ret_get_func(iterator); \ *it = array->get(0); \ @@ -2894,8 +2937,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_BEGIN_OBJECT) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); #ifdef DEBUG_ENABLED bool freed = false; @@ -2933,7 +2976,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); ip = jumpto; } else { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); *iterator = obj->callp(CoreStringNames::get_singleton()->_iter_get, (const Variant **)args, 1, ce); #ifdef DEBUG_ENABLED if (ce.error != Callable::CallError::CALL_OK) { @@ -2950,8 +2993,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); bool valid; if (!container->iter_next(*counter, valid)) { @@ -2965,7 +3008,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); ip = jumpto; } else { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); *iterator = container->iter_get(*counter, valid); #ifdef DEBUG_ENABLED @@ -2982,8 +3025,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_INT) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); int64_t size = *VariantInternal::get_int(container); int64_t *count = VariantInternal::get_int(counter); @@ -2995,7 +3038,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); ip = jumpto; } else { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); *VariantInternal::get_int(iterator) = *count; ip += 5; // Loop again. @@ -3006,8 +3049,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_FLOAT) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); double size = *VariantInternal::get_float(container); double *count = VariantInternal::get_float(counter); @@ -3019,7 +3062,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); ip = jumpto; } else { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); *VariantInternal::get_float(iterator) = *count; ip += 5; // Loop again. @@ -3030,8 +3073,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_VECTOR2) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); const Vector2 *bounds = VariantInternal::get_vector2((const Variant *)container); double *count = VariantInternal::get_float(counter); @@ -3043,7 +3086,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); ip = jumpto; } else { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); *VariantInternal::get_float(iterator) = *count; ip += 5; // Loop again. @@ -3054,8 +3097,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_VECTOR2I) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); const Vector2i *bounds = VariantInternal::get_vector2i((const Variant *)container); int64_t *count = VariantInternal::get_int(counter); @@ -3067,7 +3110,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); ip = jumpto; } else { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); *VariantInternal::get_int(iterator) = *count; ip += 5; // Loop again. @@ -3078,8 +3121,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_VECTOR3) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); const Vector3 *bounds = VariantInternal::get_vector3((const Variant *)container); double *count = VariantInternal::get_float(counter); @@ -3091,7 +3134,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); ip = jumpto; } else { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); *VariantInternal::get_float(iterator) = *count; ip += 5; // Loop again. @@ -3102,8 +3145,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_VECTOR3I) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); const Vector3i *bounds = VariantInternal::get_vector3i((const Variant *)container); int64_t *count = VariantInternal::get_int(counter); @@ -3115,7 +3158,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); ip = jumpto; } else { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); *VariantInternal::get_int(iterator) = *count; ip += 5; // Loop again. @@ -3126,8 +3169,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_STRING) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); const String *str = VariantInternal::get_string((const Variant *)container); int64_t *idx = VariantInternal::get_int(counter); @@ -3138,7 +3181,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); ip = jumpto; } else { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); *VariantInternal::get_string(iterator) = str->substr(*idx, 1); ip += 5; // Loop again. @@ -3149,8 +3192,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_DICTIONARY) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); const Dictionary *dict = VariantInternal::get_dictionary((const Variant *)container); const Variant *next = dict->next(counter); @@ -3160,7 +3203,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); ip = jumpto; } else { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); *counter = *next; *iterator = *next; @@ -3172,8 +3215,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_ARRAY) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); const Array *array = VariantInternal::get_array((const Variant *)container); int64_t *idx = VariantInternal::get_int(counter); @@ -3184,7 +3227,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); ip = jumpto; } else { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); *iterator = array->get(*idx); ip += 5; // Loop again. @@ -3195,8 +3238,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #define OPCODE_ITERATE_PACKED_ARRAY(m_var_type, m_elem_type, m_get_func, m_ret_get_func) \ OPCODE(OPCODE_ITERATE_PACKED_##m_var_type##_ARRAY) { \ CHECK_SPACE(4); \ - GET_INSTRUCTION_ARG(counter, 0); \ - GET_INSTRUCTION_ARG(container, 1); \ + GET_VARIANT_PTR(counter, 0); \ + GET_VARIANT_PTR(container, 1); \ const Vector<m_elem_type> *array = VariantInternal::m_get_func((const Variant *)container); \ int64_t *idx = VariantInternal::get_int(counter); \ (*idx)++; \ @@ -3205,7 +3248,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(jumpto<0 || jumpto> _code_size); \ ip = jumpto; \ } else { \ - GET_INSTRUCTION_ARG(iterator, 2); \ + GET_VARIANT_PTR(iterator, 2); \ *VariantInternal::m_ret_get_func(iterator) = array->get(*idx); \ ip += 5; \ } \ @@ -3225,8 +3268,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE(OPCODE_ITERATE_OBJECT) { CHECK_SPACE(4); - GET_INSTRUCTION_ARG(counter, 0); - GET_INSTRUCTION_ARG(container, 1); + GET_VARIANT_PTR(counter, 0); + GET_VARIANT_PTR(container, 1); #ifdef DEBUG_ENABLED bool freed = false; @@ -3264,7 +3307,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(jumpto < 0 || jumpto > _code_size); ip = jumpto; } else { - GET_INSTRUCTION_ARG(iterator, 2); + GET_VARIANT_PTR(iterator, 2); *iterator = obj->callp(CoreStringNames::get_singleton()->_iter_get, (const Variant **)args, 1, ce); #ifdef DEBUG_ENABLED if (ce.error != Callable::CallError::CALL_OK) { @@ -3283,7 +3326,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a 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); + GET_VARIANT_PTR(dst, 0); *dst = GDScriptLanguage::get_singleton()->get_global_array()[global_idx]; ip += 3; @@ -3297,7 +3340,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a const StringName *globalname = &_global_names_ptr[globalname_idx]; GD_ERR_BREAK(!GDScriptLanguage::get_singleton()->get_named_globals_map().has(*globalname)); - GET_INSTRUCTION_ARG(dst, 0); + GET_VARIANT_PTR(dst, 0); *dst = GDScriptLanguage::get_singleton()->get_named_globals_map()[*globalname]; ip += 3; @@ -3307,7 +3350,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #define OPCODE_TYPE_ADJUST(m_v_type, m_c_type) \ OPCODE(OPCODE_TYPE_ADJUST_##m_v_type) { \ CHECK_SPACE(2); \ - GET_INSTRUCTION_ARG(arg, 0); \ + GET_VARIANT_PTR(arg, 0); \ VariantTypeAdjust<m_c_type>::adjust(arg); \ ip += 2; \ } \ @@ -3355,13 +3398,13 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a CHECK_SPACE(3); #ifdef DEBUG_ENABLED - GET_INSTRUCTION_ARG(test, 0); + GET_VARIANT_PTR(test, 0); bool result = test->booleanize(); if (!result) { String message_str; if (_code_ptr[ip + 2] != 0) { - GET_INSTRUCTION_ARG(message, 1); + GET_VARIANT_PTR(message, 1); message_str = *message; } if (message_str.is_empty()) { diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index a0c107aa53..a6cbb7f6ae 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_warning.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_warning.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_warning.h" @@ -80,10 +80,6 @@ String GDScriptWarning::get_message() const { case STANDALONE_EXPRESSION: { return "Standalone expression (the line has no effect)."; } break; - case VOID_ASSIGNMENT: { - CHECK_SYMBOLS(1); - return "Assignment operation, but the function '" + symbols[0] + "()' returns void."; - } break; case NARROWING_CONVERSION: { return "Narrowing conversion (float is converted to int and loses precision)."; } break; @@ -96,7 +92,7 @@ String GDScriptWarning::get_message() const { } break; case RETURN_VALUE_DISCARDED: { CHECK_SYMBOLS(1); - return "The function '" + symbols[0] + "()' returns a value, but this value is never used."; + return "The function '" + symbols[0] + "()' returns a value that will be discarded if not used."; } break; case PROPERTY_USED_AS_FUNCTION: { CHECK_SYMBOLS(2); @@ -159,6 +155,10 @@ String GDScriptWarning::get_message() const { CHECK_SYMBOLS(2); return vformat(R"(The function '%s()' is a static function but was called from an instance. Instead, it should be directly called from the type: '%s.%s()'.)", symbols[0], symbols[1], symbols[0]); } + case CONFUSABLE_IDENTIFIER: { + CHECK_SYMBOLS(1); + return vformat(R"(The identifier "%s" has misleading characters and might be confused with something else.)", symbols[0]); + } case WARNING_MAX: break; // Can't happen, but silences warning } @@ -171,6 +171,10 @@ int GDScriptWarning::get_default_value(Code p_code) { if (get_name_from_code(p_code).to_lower().begins_with("unsafe_")) { return WarnLevel::IGNORE; } + // Too spammy by default on common cases (connect, Tween, etc.). + if (p_code == RETURN_VALUE_DISCARDED) { + return WarnLevel::IGNORE; + } return WarnLevel::WARN; } @@ -198,7 +202,6 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "UNREACHABLE_CODE", "UNREACHABLE_PATTERN", "STANDALONE_EXPRESSION", - "VOID_ASSIGNMENT", "NARROWING_CONVERSION", "INCOMPATIBLE_TERNARY", "UNUSED_SIGNAL", @@ -220,6 +223,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "SHADOWED_GLOBAL_IDENTIFIER", "INT_ASSIGNED_TO_ENUM", "STATIC_CALLED_ON_INSTANCE", + "CONFUSABLE_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 7e4e975510..b485f02b9c 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_warning.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_warning.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_WARNING_H #define GDSCRIPT_WARNING_H @@ -57,7 +57,6 @@ public: UNREACHABLE_CODE, // Code after a return statement. UNREACHABLE_PATTERN, // Pattern in a match statement after a catch all pattern (wildcard or bind). STANDALONE_EXPRESSION, // Expression not assigned to a variable. - VOID_ASSIGNMENT, // Function returns void but it's assigned to a variable. NARROWING_CONVERSION, // Float value into an integer slot, precision is lost. INCOMPATIBLE_TERNARY, // Possible values of a ternary if are not mutually compatible. UNUSED_SIGNAL, // Signal is defined but never emitted. @@ -79,6 +78,7 @@ public: SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable. INT_ASSIGNED_TO_ENUM, // An integer value was assigned to an enum-typed variable without casting. STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself. + CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e"). WARNING_MAX, }; diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index de3becbaf8..146ed10ceb 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_extend_parser.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_extend_parser.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_extend_parser.h" @@ -350,8 +350,8 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN if (parameter->get_datatype().is_hard_type()) { parameters += ": " + parameter->get_datatype().to_string(); } - if (parameter->default_value != nullptr) { - parameters += " = " + parameter->default_value->reduced_value.to_json_string(); + if (parameter->initializer != nullptr) { + parameters += " = " + parameter->initializer->reduced_value.to_json_string(); } } r_symbol.detail += parameters + ")"; @@ -695,8 +695,8 @@ Dictionary ExtendGDScriptParser::dump_function_api(const GDScriptParser::Functio Dictionary arg; arg["name"] = p_func->parameters[i]->identifier->name; arg["type"] = p_func->parameters[i]->get_datatype().to_string(); - if (p_func->parameters[i]->default_value != nullptr) { - arg["default_value"] = p_func->parameters[i]->default_value->reduced_value; + if (p_func->parameters[i]->initializer != nullptr) { + arg["default_value"] = p_func->parameters[i]->initializer->reduced_value; } parameters.push_back(arg); } @@ -844,8 +844,9 @@ Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) { lines = p_code.split("\n"); Error err = GDScriptParser::parse(p_code, p_path, false); + GDScriptAnalyzer analyzer(this); + if (err == OK) { - GDScriptAnalyzer analyzer(this); err = analyzer.analyze(); } update_diagnostics(); diff --git a/modules/gdscript/language_server/gdscript_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index 08bba4a2d4..4500cb01f3 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_extend_parser.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_extend_parser.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_EXTEND_PARSER_H #define GDSCRIPT_EXTEND_PARSER_H diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 39f4c976a4..acd75f039a 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -1,37 +1,38 @@ -/*************************************************************************/ -/* gdscript_language_protocol.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_language_protocol.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_language_protocol.h" #include "core/config/project_settings.h" #include "editor/doc_tools.h" +#include "editor/editor_help.h" #include "editor/editor_log.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" diff --git a/modules/gdscript/language_server/gdscript_language_protocol.h b/modules/gdscript/language_server/gdscript_language_protocol.h index 3c9cfe512f..d25814f8aa 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.h +++ b/modules/gdscript/language_server/gdscript_language_protocol.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_language_protocol.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_language_protocol.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_LANGUAGE_PROTOCOL_H #define 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 38bea314a0..062be0fe20 100644 --- a/modules/gdscript/language_server/gdscript_language_server.cpp +++ b/modules/gdscript/language_server/gdscript_language_server.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_language_server.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_language_server.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_language_server.h" diff --git a/modules/gdscript/language_server/gdscript_language_server.h b/modules/gdscript/language_server/gdscript_language_server.h index 8de72fc9c9..a130dc3ac8 100644 --- a/modules/gdscript/language_server/gdscript_language_server.h +++ b/modules/gdscript/language_server/gdscript_language_server.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_language_server.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_language_server.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_LANGUAGE_SERVER_H #define GDSCRIPT_LANGUAGE_SERVER_H diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index 3905e28bcb..b9e6921034 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_text_document.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_text_document.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_text_document.h" @@ -107,6 +107,7 @@ void GDScriptTextDocument::didSave(const Variant &p_param) { } else { scr->reload(true); } + scr->update_exports(); ScriptEditor::get_singleton()->update_docs_from_script(scr); } } diff --git a/modules/gdscript/language_server/gdscript_text_document.h b/modules/gdscript/language_server/gdscript_text_document.h index 456c7d71fe..aeda10de89 100644 --- a/modules/gdscript/language_server/gdscript_text_document.h +++ b/modules/gdscript/language_server/gdscript_text_document.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_text_document.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_text_document.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_TEXT_DOCUMENT_H #define GDSCRIPT_TEXT_DOCUMENT_H diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 390460bed9..b90c452346 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_workspace.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_workspace.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_workspace.h" diff --git a/modules/gdscript/language_server/gdscript_workspace.h b/modules/gdscript/language_server/gdscript_workspace.h index 88f3aaf957..a849ef8a8d 100644 --- a/modules/gdscript/language_server/gdscript_workspace.h +++ b/modules/gdscript/language_server/gdscript_workspace.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_workspace.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_workspace.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_WORKSPACE_H #define GDSCRIPT_WORKSPACE_H diff --git a/modules/gdscript/language_server/godot_lsp.h b/modules/gdscript/language_server/godot_lsp.h index 024da1cab7..8a033204da 100644 --- a/modules/gdscript/language_server/godot_lsp.h +++ b/modules/gdscript/language_server/godot_lsp.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* godot_lsp.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* godot_lsp.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GODOT_LSP_H #define GODOT_LSP_H @@ -702,7 +702,7 @@ struct DiagnosticRelatedInformation { Dictionary to_json() const { Dictionary dict; - dict["location"] = location.to_json(), + dict["location"] = location.to_json(); dict["message"] = message; return dict; } diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index 19a8b59c6f..b6feaadccf 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* register_types.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* register_types.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "register_types.h" @@ -71,21 +71,18 @@ class EditorExportGDScript : public EditorExportPlugin { public: virtual void _export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) override { - int script_mode = EditorExportPreset::MODE_SCRIPT_COMPILED; String script_key; const Ref<EditorExportPreset> &preset = get_export_preset(); if (preset.is_valid()) { - script_mode = preset->get_script_export_mode(); script_key = preset->get_script_encryption_key().to_lower(); } - if (!p_path.ends_with(".gd") || script_mode == EditorExportPreset::MODE_SCRIPT_TEXT) { + if (!p_path.ends_with(".gd")) { return; } - // TODO: Re-add compiled GDScript on export. return; } diff --git a/modules/gdscript/register_types.h b/modules/gdscript/register_types.h index a7e6b02dcf..f7ec521ec8 100644 --- a/modules/gdscript/register_types.h +++ b/modules/gdscript/register_types.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* register_types.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* register_types.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_REGISTER_TYPES_H #define GDSCRIPT_REGISTER_TYPES_H diff --git a/modules/gdscript/tests/README.md b/modules/gdscript/tests/README.md index 6e54085962..361d586d32 100644 --- a/modules/gdscript/tests/README.md +++ b/modules/gdscript/tests/README.md @@ -4,5 +4,5 @@ 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) +[Integration tests for GDScript documentation](https://docs.godotengine.org/en/latest/contributing/development/core_and_modules/unit_testing.html#integration-tests-for-gdscript) for information about creating and running GDScript integration tests. diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 15131afde7..d2c8b5c317 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_test_runner.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_test_runner.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "gdscript_test_runner.h" @@ -71,27 +71,38 @@ void init_autoloads() { continue; } - Ref<Resource> res = ResourceLoader::load(info.path); - ERR_CONTINUE_MSG(res.is_null(), "Can't autoload: " + info.path); Node *n = nullptr; - Ref<PackedScene> scn = res; - Ref<Script> script = res; - if (scn.is_valid()) { - n = scn->instantiate(); - } else if (script.is_valid()) { - StringName ibt = script->get_instance_base_type(); - bool valid_type = ClassDB::is_parent_class(ibt, "Node"); - ERR_CONTINUE_MSG(!valid_type, "Script does not inherit from Node: " + info.path); + if (ResourceLoader::get_resource_type(info.path) == "PackedScene") { + // Cache the scene reference before loading it (for cyclic references) + Ref<PackedScene> scn; + scn.instantiate(); + scn->set_path(info.path); + scn->reload_from_file(); + ERR_CONTINUE_MSG(!scn.is_valid(), vformat("Can't autoload: %s.", info.path)); + + if (scn.is_valid()) { + n = scn->instantiate(); + } + } else { + Ref<Resource> res = ResourceLoader::load(info.path); + ERR_CONTINUE_MSG(res.is_null(), vformat("Can't autoload: %s.", info.path)); - Object *obj = ClassDB::instantiate(ibt); + Ref<Script> scr = res; + if (scr.is_valid()) { + StringName ibt = scr->get_instance_base_type(); + bool valid_type = ClassDB::is_parent_class(ibt, "Node"); + ERR_CONTINUE_MSG(!valid_type, vformat("Script does not inherit from Node: %s.", info.path)); - ERR_CONTINUE_MSG(!obj, "Cannot instance script for autoload, expected 'Node' inheritance, got: " + String(ibt) + "."); + Object *obj = ClassDB::instantiate(ibt); - n = Object::cast_to<Node>(obj); - n->set_script(script); + ERR_CONTINUE_MSG(!obj, vformat("Cannot instance script for Autoload, expected 'Node' inheritance, got: %s.", ibt)); + + n = Object::cast_to<Node>(obj); + n->set_script(scr); + } } - ERR_CONTINUE_MSG(!n, "Path in autoload not a node or script: " + info.path); + ERR_CONTINUE_MSG(!n, vformat("Path in autoload not a node or script: %s.", info.path)); n->set_name(info.name); for (int i = 0; i < ScriptServer::get_language_count(); i++) { @@ -251,7 +262,10 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) { return false; } } else { - if (next.get_extension().to_lower() == "gd") { + if (next.ends_with(".notest.gd")) { + next = dir->get_next(); + continue; + } else if (next.get_extension().to_lower() == "gd") { #ifndef DEBUG_ENABLED // On release builds, skip tests marked as debug only. Error open_err = OK; @@ -461,7 +475,6 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { Ref<GDScript> script; script.instantiate(); script->set_path(source_file); - script->set_script_path(source_file); err = script->load_source_code(source_file); if (err != OK) { enable_stdout(); @@ -598,6 +611,9 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { } enable_stdout(); + + GDScriptCache::remove_script(script->get_path()); + return result; } diff --git a/modules/gdscript/tests/gdscript_test_runner.h b/modules/gdscript/tests/gdscript_test_runner.h index 033d2fcad1..b097f1b485 100644 --- a/modules/gdscript/tests/gdscript_test_runner.h +++ b/modules/gdscript/tests/gdscript_test_runner.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_test_runner.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_test_runner.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_TEST_RUNNER_H #define GDSCRIPT_TEST_RUNNER_H diff --git a/modules/gdscript/tests/gdscript_test_runner_suite.h b/modules/gdscript/tests/gdscript_test_runner_suite.h index 90f6d7f7e8..aed0ac2baf 100644 --- a/modules/gdscript/tests/gdscript_test_runner_suite.h +++ b/modules/gdscript/tests/gdscript_test_runner_suite.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* gdscript_test_runner_suite.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* gdscript_test_runner_suite.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef GDSCRIPT_TEST_RUNNER_SUITE_H #define GDSCRIPT_TEST_RUNNER_SUITE_H diff --git a/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.gd b/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.gd new file mode 100644 index 0000000000..38c2faa859 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.gd @@ -0,0 +1,2 @@ +func test(): + CanvasItem.new() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.out b/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.out new file mode 100644 index 0000000000..9eff912b59 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/abstract_class_instantiate.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Native class "CanvasItem" cannot be constructed as it is abstract. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.gd b/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.gd new file mode 100644 index 0000000000..118e7e8a45 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.gd @@ -0,0 +1,9 @@ +class A extends CanvasItem: + func _init(): + print('no') + +class B extends A: + pass + +func test(): + B.new() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.out b/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.out new file mode 100644 index 0000000000..8b956f5974 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/abstract_script_instantiate.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Class "abstract_script_instantiate.gd::B" cannot be constructed as it is based on abstract native class "CanvasItem". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.gd b/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.gd new file mode 100644 index 0000000000..75524c32ae --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.gd @@ -0,0 +1,6 @@ +var num := 1 + +@export_range(num, 10) var a + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.out b/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.out new file mode 100644 index 0000000000..b4f0e79237 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Argument 1 of annotation "@export_range" isn't a constant expression. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_enum.gd b/modules/gdscript/tests/scripts/analyzer/errors/assign_enum.gd new file mode 100644 index 0000000000..8123fc53d9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_enum.gd @@ -0,0 +1,3 @@ +enum { V } +func test(): + V = 1 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_enum.out b/modules/gdscript/tests/scripts/analyzer/errors/assign_enum.out new file mode 100644 index 0000000000..5275183da2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_enum.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/assign_named_enum.gd b/modules/gdscript/tests/scripts/analyzer/errors/assign_named_enum.gd new file mode 100644 index 0000000000..da2b13d690 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_named_enum.gd @@ -0,0 +1,3 @@ +enum NamedEnum { V } +func test(): + NamedEnum.V = 1 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_named_enum.out b/modules/gdscript/tests/scripts/analyzer/errors/assign_named_enum.out new file mode 100644 index 0000000000..5275183da2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_named_enum.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/assign_signal.gd b/modules/gdscript/tests/scripts/analyzer/errors/assign_signal.gd new file mode 100644 index 0000000000..0e1f7256f8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_signal.gd @@ -0,0 +1,4 @@ +signal your_base +signal my_base +func test(): + your_base = my_base diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assign_signal.out b/modules/gdscript/tests/scripts/analyzer/errors/assign_signal.out new file mode 100644 index 0000000000..5275183da2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/assign_signal.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/cast_enum_bad_enum.gd b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.gd new file mode 100644 index 0000000000..71616ea3af --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.gd @@ -0,0 +1,5 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } +enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2, OTHER_ENUM_VALUE_3 } + +func test(): + print(MyOtherEnum.OTHER_ENUM_VALUE_3 as MyEnum) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.out b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.out new file mode 100644 index 0000000000..3a8d2a205a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid cast. Enum "cast_enum_bad_enum.gd::MyEnum" does not have value corresponding to "MyOtherEnum.OTHER_ENUM_VALUE_3" (2). diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.gd b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.gd new file mode 100644 index 0000000000..60a31fb318 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.gd @@ -0,0 +1,4 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } + +func test(): + print(2 as MyEnum) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.out b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.out new file mode 100644 index 0000000000..bc0d8b7834 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid cast. Enum "cast_enum_bad_int.gd::MyEnum" does not have enum value 2. 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 index 87863baf75..b9a1d301ad 100644 --- 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 @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The member "Vector2" cannot have the same name as a builtin type. +Class "Vector2" hides a built-in type. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_array_index_assign.gd b/modules/gdscript/tests/scripts/analyzer/errors/constant_array_index_assign.gd new file mode 100644 index 0000000000..b8603dd4ca --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_array_index_assign.gd @@ -0,0 +1,5 @@ +const array: Array = [0] + +func test(): + var key: int = 0 + array[key] = 0 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_array_index_assign.out b/modules/gdscript/tests/scripts/analyzer/errors/constant_array_index_assign.out new file mode 100644 index 0000000000..5275183da2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_array_index_assign.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/constant_dictionary_index_assign.gd b/modules/gdscript/tests/scripts/analyzer/errors/constant_dictionary_index_assign.gd new file mode 100644 index 0000000000..9b5112b788 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_dictionary_index_assign.gd @@ -0,0 +1,5 @@ +const dictionary := {} + +func test(): + var key: int = 0 + dictionary[key] = 0 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_dictionary_index_assign.out b/modules/gdscript/tests/scripts/analyzer/errors/constant_dictionary_index_assign.out new file mode 100644 index 0000000000..5275183da2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_dictionary_index_assign.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/constant_subscript_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/constant_subscript_type.gd new file mode 100644 index 0000000000..87fbe1229c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_subscript_type.gd @@ -0,0 +1,5 @@ +const base := [0] + +func test(): + var sub := base[0] + if sub is String: pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constant_subscript_type.out b/modules/gdscript/tests/scripts/analyzer/errors/constant_subscript_type.out new file mode 100644 index 0000000000..54c190cf8a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/constant_subscript_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Expression is of type "int" so it can't be of type "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constructor_call_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/constructor_call_type.gd new file mode 100644 index 0000000000..251be70088 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/constructor_call_type.gd @@ -0,0 +1,10 @@ +class A: + func _init(): + pass + +class B extends A: pass +class C extends A: pass + +func test(): + var x := B.new() + print(x is C) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/constructor_call_type.out b/modules/gdscript/tests/scripts/analyzer/errors/constructor_call_type.out new file mode 100644 index 0000000000..91d5125ec0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/constructor_call_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Expression is of type "B" so it can't be of type "C". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_inheritance.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_inheritance.gd new file mode 100644 index 0000000000..d2f6404cd2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_inheritance.gd @@ -0,0 +1,8 @@ +func test(): + print(InnerA.new()) + +class InnerA extends InnerB: + pass + +class InnerB extends InnerA: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_inheritance.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_inheritance.out new file mode 100644 index 0000000000..75a94baa17 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_inheritance.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cyclic inheritance. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_const.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_const.gd new file mode 100644 index 0000000000..4292534951 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_const.gd @@ -0,0 +1,5 @@ +func test(): + print(c1) + +const c1 = c2 +const c2 = c1 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_const.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_const.out new file mode 100644 index 0000000000..e71b3fc56a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_const.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Could not resolve member "c1": Cyclic reference. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum.gd new file mode 100644 index 0000000000..1caef3d366 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum.gd @@ -0,0 +1,5 @@ +func test(): + print(E1.V) + +enum E1 {V = E2.V} +enum E2 {V = E1.V} diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum.out new file mode 100644 index 0000000000..1b6569ba3a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Could not resolve member "E1": Cyclic reference. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum_value.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum_value.gd new file mode 100644 index 0000000000..237758f340 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum_value.gd @@ -0,0 +1,5 @@ +func test(): + print(EV1) + +enum {EV1 = EV2} +enum {EV2 = EV1} diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum_value.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum_value.out new file mode 100644 index 0000000000..233f5fee25 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_enum_value.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Could not resolve member "EV1": Cyclic reference. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.gd new file mode 100644 index 0000000000..52e0d60389 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.gd @@ -0,0 +1,6 @@ +func test(): + print(v) + +var v = A.v + +const A = preload("cyclic_ref_external_a.notest.gd") diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.out new file mode 100644 index 0000000000..64a6bd417d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Could not resolve member "v". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external_a.notest.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external_a.notest.gd new file mode 100644 index 0000000000..9ef1769250 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external_a.notest.gd @@ -0,0 +1,3 @@ +const B = preload("cyclic_ref_external.gd") + +var v = B.v diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_func.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_func.gd new file mode 100644 index 0000000000..b610464c44 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_func.gd @@ -0,0 +1,9 @@ +func test(): + print(f1()) + print(f2()) + +static func f1(p := f2()) -> int: + return 1 + +static func f2(p := f1()) -> int: + return 2 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_func.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_func.out new file mode 100644 index 0000000000..d3ec4b0692 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_func.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Could not resolve member "f1": Cyclic reference. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_override.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_override.gd new file mode 100644 index 0000000000..f750715838 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_override.gd @@ -0,0 +1,12 @@ +func test(): + print(v) + +var v := InnerA.new().f() + +class InnerA: + func f(p := InnerB.new().f()) -> int: + return 1 + +class InnerB extends InnerA: + func f(p := 1) -> int: + return super.f() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_override.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_override.out new file mode 100644 index 0000000000..6bca25b330 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_override.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Could not resolve member "f": Cyclic reference. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var.gd b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var.gd new file mode 100644 index 0000000000..6913888724 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var.gd @@ -0,0 +1,5 @@ +func test(): + print(v1) + +var v1 := v2 +var v2 := v1 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var.out new file mode 100644 index 0000000000..c337882d9c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_var.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Could not resolve member "v1": Cyclic reference. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.gd new file mode 100644 index 0000000000..4dd2b556ee --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.gd @@ -0,0 +1,9 @@ +# https://github.com/godotengine/godot/issues/62957 + +func test(): + var dict = { + &"key": "StringName", + "key": "String" + } + + print("Invalid dictionary: %s" % dict) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.out new file mode 100644 index 0000000000..189d8a7955 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Key "key" was already used in this dictionary (at line 5). diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_method.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_method.gd new file mode 100644 index 0000000000..2940c03515 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_method.gd @@ -0,0 +1,4 @@ +enum Enum {V1, V2} + +func test(): + Enum.clear() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_method.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_method.out new file mode 100644 index 0000000000..9ca86eca9c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_method.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot call non-const Dictionary function "clear()" on enum "Enum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.gd new file mode 100644 index 0000000000..a66e2714d9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.gd @@ -0,0 +1,4 @@ +enum Enum {V1, V2} + +func test(): + var bad = Enum.V3 diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.out new file mode 100644 index 0000000000..ddbdc17a42 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_bad_value.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot find member "V3" in base "enum_bad_value.gd::Enum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out index fde7e92f8c..02c4633586 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot assign a value of type "MyOtherEnum (enum)" to a target of type "MyEnum (enum)". +Value of type "enum_class_var_assign_with_wrong_enum_type.gd::MyOtherEnum" cannot be assigned to a variable of type "enum_class_var_assign_with_wrong_enum_type.gd::MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out index b1710c798d..441cccbf7b 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Value of type "MyOtherEnum (enum)" cannot be assigned to a variable of type "MyEnum (enum)". +Cannot assign a value of type enum_class_var_init_with_wrong_enum_type.gd::MyOtherEnum to variable "class_var" with specified type enum_class_var_init_with_wrong_enum_type.gd::MyEnum. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_duplicate_bad_method.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_duplicate_bad_method.gd new file mode 100644 index 0000000000..2c7dfafd06 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_duplicate_bad_method.gd @@ -0,0 +1,5 @@ +enum Enum {V1, V2} + +func test(): + var Enum2 = Enum + Enum2.clear() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_duplicate_bad_method.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_duplicate_bad_method.out new file mode 100644 index 0000000000..9ca86eca9c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_duplicate_bad_method.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot call non-const Dictionary function "clear()" on enum "Enum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.gd new file mode 100644 index 0000000000..62ac1c3108 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.gd @@ -0,0 +1,8 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } +enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 } + +func enum_func(e : MyEnum) -> void: + print(e) + +func test(): + enum_func(MyOtherEnum.OTHER_ENUM_VALUE_1) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out new file mode 100644 index 0000000000..e85f7d6f9f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid argument for "enum_func()" function: argument 1 should be "enum_function_parameter_wrong_type.gd::MyEnum" but is "enum_function_parameter_wrong_type.gd::MyOtherEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.gd new file mode 100644 index 0000000000..18b3ffb0fc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.gd @@ -0,0 +1,8 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } +enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 } + +func enum_func() -> MyEnum: + return MyOtherEnum.OTHER_ENUM_VALUE_1 + +func test(): + print(enum_func()) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out new file mode 100644 index 0000000000..f7ea3267fa --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot return value of type "enum_function_return_wrong_type.gd::MyOtherEnum" because the function return type is "enum_function_return_wrong_type.gd::MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.gd new file mode 100644 index 0000000000..2b006f1f69 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.gd @@ -0,0 +1,10 @@ +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } + +class InnerClass: + enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } + +func test(): + var local_var: MyEnum = MyEnum.ENUM_VALUE_1 + print(local_var) + local_var = InnerClass.MyEnum.ENUM_VALUE_2 + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out new file mode 100644 index 0000000000..38df5a0cd8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Value of type "enum_local_var_assign_outer_with_wrong_enum_type.gd::InnerClass::MyEnum" cannot be assigned to a variable of type "enum_local_var_assign_outer_with_wrong_enum_type.gd::MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out index fde7e92f8c..2adcbd9edf 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot assign a value of type "MyOtherEnum (enum)" to a target of type "MyEnum (enum)". +Value of type "enum_local_var_assign_with_wrong_enum_type.gd::MyOtherEnum" cannot be assigned to a variable of type "enum_local_var_assign_with_wrong_enum_type.gd::MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out index b1710c798d..331113dd30 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Value of type "MyOtherEnum (enum)" cannot be assigned to a variable of type "MyEnum (enum)". +Cannot assign a value of type enum_local_var_init_with_wrong_enum_type.gd::MyOtherEnum to variable "local_var" with specified type enum_local_var_init_with_wrong_enum_type.gd::MyEnum. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.gd new file mode 100644 index 0000000000..744c2e47ce --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.gd @@ -0,0 +1,2 @@ +func test(): + var _bad = TileSet.TileShape.THIS_DOES_NOT_EXIST diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.out new file mode 100644 index 0000000000..49f041a2dd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_native_bad_value.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot find member "THIS_DOES_NOT_EXIST" in base "TileSet::TileShape". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.gd new file mode 100644 index 0000000000..81d5d59ae8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.gd @@ -0,0 +1,7 @@ +enum MyEnum { VALUE_A, VALUE_B, VALUE_C = 42 } + +func test(): + const P = preload("../features/enum_value_from_parent.gd") + var local_var: MyEnum + local_var = P.VALUE_B + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out new file mode 100644 index 0000000000..6298c026b4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Value of type "enum_value_from_parent.gd::<anonymous enum>" cannot be assigned to a variable of type "enum_preload_unnamed_assign_to_named.gd::MyEnum". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_base_enum.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_base_enum.gd new file mode 100644 index 0000000000..96904c297a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_base_enum.gd @@ -0,0 +1,8 @@ +class A: + enum { V } + +class B extends A: + enum { V } + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_base_enum.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_base_enum.out new file mode 100644 index 0000000000..7961a1a481 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_shadows_base_enum.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The member "V" already exists in parent class A. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.gd new file mode 100644 index 0000000000..7e749db6b5 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.gd @@ -0,0 +1,7 @@ +enum { ENUM_VALUE_1, ENUM_VALUE_2 } + +enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 } + +func test(): + var local_var: MyEnum = ENUM_VALUE_1 + print(local_var) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out new file mode 100644 index 0000000000..b70121ed81 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a value of type enum_unnamed_assign_to_named.gd::<anonymous enum> to variable "local_var" with specified type enum_unnamed_assign_to_named.gd::MyEnum. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_float.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_float.gd new file mode 100644 index 0000000000..cf56a0a933 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_float.gd @@ -0,0 +1,6 @@ +const constant_float = 1.0 + +func test(): + for x in constant_float: + if x is String: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_float.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_float.out new file mode 100644 index 0000000000..e309831b3e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_float.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Expression is of type "float" so it can't be of type "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_int.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_int.gd new file mode 100644 index 0000000000..5ee8ac19e1 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_int.gd @@ -0,0 +1,6 @@ +const constant_int = 1 + +func test(): + for x in constant_int: + if x is String: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_int.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_int.out new file mode 100644 index 0000000000..54c190cf8a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_constant_int.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Expression is of type "int" so it can't be of type "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_enum_value.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_enum_value.gd new file mode 100644 index 0000000000..b3db4f3b49 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_enum_value.gd @@ -0,0 +1,6 @@ +enum { enum_value = 1 } + +func test(): + for x in enum_value: + if x is String: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_enum_value.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_enum_value.out new file mode 100644 index 0000000000..54c190cf8a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_enum_value.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Expression is of type "int" so it can't be of type "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_float.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_float.gd new file mode 100644 index 0000000000..87c54f7402 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_float.gd @@ -0,0 +1,6 @@ +func test(): + var hard_float := 1.0 + + for x in hard_float: + if x is String: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_float.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_float.out new file mode 100644 index 0000000000..e309831b3e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_float.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Expression is of type "float" so it can't be of type "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_int.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_int.gd new file mode 100644 index 0000000000..2a43f5a930 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_int.gd @@ -0,0 +1,6 @@ +func test(): + var hard_int := 1 + + for x in hard_int: + if x is String: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_int.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_int.out new file mode 100644 index 0000000000..54c190cf8a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_int.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Expression is of type "int" so it can't be of type "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_iterator.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_iterator.gd new file mode 100644 index 0000000000..c3920d35b3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_iterator.gd @@ -0,0 +1,14 @@ +class Iterator: + func _iter_init(_count): + return true + func _iter_next(_count): + return false + func _iter_get(_count) -> StringName: + return &'custom' + +func test(): + var hard_iterator := Iterator.new() + + for x in hard_iterator: + if x is int: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_iterator.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_iterator.out new file mode 100644 index 0000000000..a48591a3b4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_iterator.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Expression is of type "StringName" so it can't be of type "int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_string.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_string.gd new file mode 100644 index 0000000000..b36d87aabe --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_string.gd @@ -0,0 +1,6 @@ +func test(): + var hard_string := 'a' + + for x in hard_string: + if x is int: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_string.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_string.out new file mode 100644 index 0000000000..92c5ebc599 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_hard_string.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Expression is of type "String" so it can't be of type "int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_bool.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_bool.gd new file mode 100644 index 0000000000..060a8bedf9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_bool.gd @@ -0,0 +1,3 @@ +func test(): + for x in true: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_bool.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_bool.out new file mode 100644 index 0000000000..94cb038885 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_bool.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Unable to iterate on value of type "bool". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_int.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_int.gd new file mode 100644 index 0000000000..6cfc822482 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_int.gd @@ -0,0 +1,4 @@ +func test(): + for x in 1: + if x is String: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_int.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_int.out new file mode 100644 index 0000000000..54c190cf8a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_on_literal_int.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Expression is of type "int" so it can't be of type "String". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out index 3baeb17066..4ccd2da381 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function(int)". +The function signature doesn't match the parent. Parent signature is "my_function(int) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out index 3baeb17066..4ccd2da381 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function(int)". +The function signature doesn't match the parent. Parent signature is "my_function(int) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out index 665c229339..c70a1df10d 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function(int = default)". +The function signature doesn't match the parent. Parent signature is "my_function(int = default) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out index 3baeb17066..4ccd2da381 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function(int)". +The function signature doesn't match the parent. Parent signature is "my_function(int) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out index 5b22739a93..61004ff627 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function()". +The function signature doesn't match the parent. Parent signature is "my_function() -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/incompatible_assignment.gd b/modules/gdscript/tests/scripts/analyzer/errors/incompatible_assignment.gd new file mode 100644 index 0000000000..664e364493 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/incompatible_assignment.gd @@ -0,0 +1,3 @@ +func test(): + var foo: bool = true + foo += 'bar' diff --git a/modules/gdscript/tests/scripts/analyzer/errors/incompatible_assignment.out b/modules/gdscript/tests/scripts/analyzer/errors/incompatible_assignment.out new file mode 100644 index 0000000000..358b096a64 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/incompatible_assignment.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid operands "bool" and "String" for assignment operator. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.gd b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.gd new file mode 100644 index 0000000000..6014ee831c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.gd @@ -0,0 +1,3 @@ +func test(): + var untyped = 1 + var inferred := untyped diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.out b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.out new file mode 100644 index 0000000000..b6dc6d0b01 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot infer the type of "inferred" variable because the value doesn't have a set type. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.gd b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.gd new file mode 100644 index 0000000000..040aa2e82a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.gd @@ -0,0 +1,5 @@ +var untyped = 1 +var inferred := untyped + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.out b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.out new file mode 100644 index 0000000000..b6dc6d0b01 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot infer the type of "inferred" variable because the value doesn't have a set type. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.gd b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.gd new file mode 100644 index 0000000000..80c676488e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.gd @@ -0,0 +1,5 @@ +func check(untyped = 1, inferred := untyped): + pass + +func test(): + check() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.out b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.out new file mode 100644 index 0000000000..8c9f0c13ae --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot infer the type of "inferred" parameter because the value doesn't have a set type. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/lambda_no_return.gd b/modules/gdscript/tests/scripts/analyzer/errors/lambda_no_return.gd new file mode 100644 index 0000000000..70973c33d4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/lambda_no_return.gd @@ -0,0 +1,4 @@ +func test(): + var lambda := func() -> int: + print('no return') + lambda.call() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/lambda_no_return.out b/modules/gdscript/tests/scripts/analyzer/errors/lambda_no_return.out new file mode 100644 index 0000000000..fe1472c54d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/lambda_no_return.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Not all code paths return a value. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/lambda_wrong_return.gd b/modules/gdscript/tests/scripts/analyzer/errors/lambda_wrong_return.gd new file mode 100644 index 0000000000..3c247a5b02 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/lambda_wrong_return.gd @@ -0,0 +1,4 @@ +func test(): + var lambda := func() -> int: + return 'string' + print(lambda.call()) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/lambda_wrong_return.out b/modules/gdscript/tests/scripts/analyzer/errors/lambda_wrong_return.out new file mode 100644 index 0000000000..53e2b012e6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/lambda_wrong_return.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot return value of type "String" because the function return type is "int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/native_type_errors.gd b/modules/gdscript/tests/scripts/analyzer/errors/native_type_errors.gd new file mode 100644 index 0000000000..e1bed94406 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/native_type_errors.gd @@ -0,0 +1,2 @@ +func test(): + TileSet.this_does_not_exist # Does not exist diff --git a/modules/gdscript/tests/scripts/analyzer/errors/native_type_errors.out b/modules/gdscript/tests/scripts/analyzer/errors/native_type_errors.out new file mode 100644 index 0000000000..06180c3a55 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/native_type_errors.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot find member "this_does_not_exist" in base "TileSet". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.gd b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.gd new file mode 100644 index 0000000000..1cf3870a8e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.gd @@ -0,0 +1,8 @@ +class Outer: + const OUTER_CONST: = 0 + class Inner: + pass + +func test() -> void: + var type: = Outer.Inner + print(type.OUTER_CONST) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out new file mode 100644 index 0000000000..73a54d7820 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> analyzer/errors/outer_class_constants.gd +>> 8 +>> Invalid get index 'OUTER_CONST' (on base: 'GDScript'). diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.gd b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.gd new file mode 100644 index 0000000000..c1074df915 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.gd @@ -0,0 +1,9 @@ +class Outer: + const OUTER_CONST: = 0 + class Inner: + pass + +func test() -> void: + var type: = Outer.Inner + var type_v: Variant = type + print(type_v.OUTER_CONST) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out new file mode 100644 index 0000000000..92e7b9316e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> analyzer/errors/outer_class_constants_as_variant.gd +>> 9 +>> Invalid get index 'OUTER_CONST' (on base: 'GDScript'). diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.gd b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.gd new file mode 100644 index 0000000000..2631c3c500 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.gd @@ -0,0 +1,8 @@ +class Outer: + const OUTER_CONST: = 0 + class Inner: + pass + +func test() -> void: + var instance: = Outer.Inner.new() + print(instance.OUTER_CONST) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out new file mode 100644 index 0000000000..892f8e2c3f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> analyzer/errors/outer_class_instance_constants.gd +>> 8 +>> Invalid get index 'OUTER_CONST' (on base: 'RefCounted (Inner)'). diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.gd b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.gd new file mode 100644 index 0000000000..cba788381e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.gd @@ -0,0 +1,9 @@ +class Outer: + const OUTER_CONST: = 0 + class Inner: + pass + +func test() -> void: + var instance: = Outer.Inner.new() + var instance_v: Variant = instance + print(instance_v.OUTER_CONST) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out new file mode 100644 index 0000000000..8257e74f57 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> analyzer/errors/outer_class_instance_constants_as_variant.gd +>> 9 +>> Invalid get index 'OUTER_CONST' (on base: 'RefCounted (Inner)'). diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_lookup.gd b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_lookup.gd new file mode 100644 index 0000000000..200c352223 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_lookup.gd @@ -0,0 +1,12 @@ +class A: + class B: + func test(): + print(A.B.D) + +class C: + class D: + pass + +func test(): + var inst = A.B.new() + inst.test() diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_lookup.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_lookup.out new file mode 100644 index 0000000000..6baed366f6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_lookup.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot find member "D" in base "B". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.gd b/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.gd new file mode 100644 index 0000000000..5c8b9fa4ae --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.gd @@ -0,0 +1,6 @@ +extends Node + +var script: int + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.out b/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.out new file mode 100644 index 0000000000..8454aaa404 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Member "script" redefined (original in native class 'Node') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.gd b/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.gd new file mode 100644 index 0000000000..4e75ded96a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.gd @@ -0,0 +1,6 @@ +enum LocalNamed { VALUE_A, VALUE_B, VALUE_C = 42 } + +func test(): + const P = preload("../features/enum_from_outer.gd") + var x : LocalNamed + x = P.Named.VALUE_A diff --git a/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out b/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out new file mode 100644 index 0000000000..5e3c446bf6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Value of type "enum_from_outer.gd::Named" cannot be assigned to a variable of type "preload_enum_error.gd::LocalNamed". 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 index bbadf1ce27..bf776029b9 100644 --- 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 @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Cannot assign a value of type "String" to a target of type "int". +Value of type "String" cannot be assigned to a variable of type "int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/return_null_in_void_func.gd b/modules/gdscript/tests/scripts/analyzer/errors/return_null_in_void_func.gd new file mode 100644 index 0000000000..393b66c9f0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/return_null_in_void_func.gd @@ -0,0 +1,2 @@ +func test() -> void: + return null diff --git a/modules/gdscript/tests/scripts/analyzer/errors/return_null_in_void_func.out b/modules/gdscript/tests/scripts/analyzer/errors/return_null_in_void_func.out new file mode 100644 index 0000000000..3c09f44ba9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/return_null_in_void_func.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +A void function cannot return a value. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/return_variant_in_void_func.gd b/modules/gdscript/tests/scripts/analyzer/errors/return_variant_in_void_func.gd new file mode 100644 index 0000000000..6be2730bab --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/return_variant_in_void_func.gd @@ -0,0 +1,4 @@ +func test() -> void: + var a + a = 1 + return a diff --git a/modules/gdscript/tests/scripts/analyzer/errors/return_variant_in_void_func.out b/modules/gdscript/tests/scripts/analyzer/errors/return_variant_in_void_func.out new file mode 100644 index 0000000000..3c09f44ba9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/return_variant_in_void_func.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +A void function cannot return a value. 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 index 9eb2a42ccd..2857cd53c8 100644 --- 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 @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Value of type "int" cannot be assigned to a variable of type "String". +Cannot assign a value of type int to variable "x" with specified type String. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_builtin_method.gd b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_builtin_method.gd new file mode 100644 index 0000000000..a3450966cc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_builtin_method.gd @@ -0,0 +1,3 @@ +func test(): + var builtin := [] + print(builtin.reverse()) # Built-in type method. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_builtin_method.out b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_builtin_method.out new file mode 100644 index 0000000000..225c85e9c7 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_builtin_method.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot get return value of call to "reverse()" because it returns "void". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_custom_method.gd b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_custom_method.gd new file mode 100644 index 0000000000..2162a181ac --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_custom_method.gd @@ -0,0 +1,5 @@ +func foo() -> void: + pass + +func test(): + print(foo()) # Custom method. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_custom_method.out b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_custom_method.out new file mode 100644 index 0000000000..2b1a607883 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_custom_method.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot get return value of call to "foo()" because it returns "void". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_gd_utility.gd b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_gd_utility.gd new file mode 100644 index 0000000000..f3443d985e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_gd_utility.gd @@ -0,0 +1,2 @@ +func test(): + print(print_debug()) # GDScript utility function. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_gd_utility.out b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_gd_utility.out new file mode 100644 index 0000000000..502c18ab9d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_gd_utility.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot get return value of call to "print_debug()" because it returns "void". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_native_method.gd b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_native_method.gd new file mode 100644 index 0000000000..b8e81b160a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_native_method.gd @@ -0,0 +1,3 @@ +func test(): + var obj := Node.new() + print(obj.free()) # Native type method. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_native_method.out b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_native_method.out new file mode 100644 index 0000000000..88be39345b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_native_method.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot get return value of call to "free()" because it returns "void". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_utility.gd b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_utility.gd new file mode 100644 index 0000000000..8eabed4271 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_utility.gd @@ -0,0 +1,2 @@ +func test(): + print(print()) # Built-in utility function. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_utility.out b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_utility.out new file mode 100644 index 0000000000..ebf43186be --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/use_value_of_void_function_utility.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot get return value of call to "print()" because it returns "void". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.gd b/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.gd new file mode 100644 index 0000000000..28561ff94b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.gd @@ -0,0 +1,9 @@ +func test(): + pass + +class A: + func overload_me(): + pass + +class B extends A: + var overload_me diff --git a/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.out b/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.out new file mode 100644 index 0000000000..32357f9f6a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The member "overload_me" already exists in parent class A. diff --git a/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.gd b/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.gd new file mode 100644 index 0000000000..272dce8bbe --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.gd @@ -0,0 +1,10 @@ +const BEFORE = 1 + +@export_range(-10, 10) var a = 0 +@export_range(1 + 2, absi(-10) + 1) var b = 5 +@export_range(BEFORE + 1, BEFORE + AFTER + 1) var c = 5 + +const AFTER = 10 + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.out b/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.out index d73c5eb7cd..d73c5eb7cd 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.out +++ b/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.out diff --git a/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.gd new file mode 100644 index 0000000000..eb0003eed8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.gd @@ -0,0 +1,15 @@ + +var m_string_array: Array[String] = [&"abc"] +var m_stringname_array: Array[StringName] = ["abc"] + +func test(): + print(m_string_array) + print(m_stringname_array) + + # Converted to String when initialized + var string_array: Array[String] = [&"abc"] + print(string_array) + + # Converted to StringName when initialized + var stringname_array: Array[StringName] = ["abc"] + print(stringname_array) diff --git a/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.out new file mode 100644 index 0000000000..09c199bde1 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.out @@ -0,0 +1,5 @@ +GDTEST_OK +["abc"] +[&"abc"] +["abc"] +[&"abc"] diff --git a/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd new file mode 100644 index 0000000000..595563541f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd @@ -0,0 +1,14 @@ +func test(): + var two: Variant = 0 + two += 2 + print(two) + + var three_0: Variant = 1 + var three_1: int = 2 + three_0 += three_1 + print(three_0) + + var four_0: int = 3 + var four_1: Variant = 1 + four_0 += four_1 + print(four_0) diff --git a/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out new file mode 100644 index 0000000000..0ddfa4b75f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out @@ -0,0 +1,4 @@ +GDTEST_OK +2 +3 +4 diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.gd new file mode 100644 index 0000000000..a94487d989 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.gd @@ -0,0 +1,13 @@ +const A: = preload("base_outer_resolution_a.notest.gd") +const B: = preload("base_outer_resolution_b.notest.gd") +const C: = preload("base_outer_resolution_c.notest.gd") + +const Extend: = preload("base_outer_resolution_extend.notest.gd") + +func test() -> void: + Extend.test_a(A.new()) + Extend.test_b(B.new()) + Extend.InnerClass.test_c(C.new()) + Extend.InnerClass.InnerInnerClass.test_a_b_c(A.new(), B.new(), C.new()) + Extend.InnerClass.InnerInnerClass.test_enum(C.TestEnum.HELLO_WORLD) + Extend.InnerClass.InnerInnerClass.test_a_prime(A.APrime.new()) diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.out b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.out new file mode 100644 index 0000000000..bd27bd31f6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.out @@ -0,0 +1,7 @@ +GDTEST_OK +true +true +true +true +true +true diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_a.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_a.notest.gd new file mode 100644 index 0000000000..966c8bfc8f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_a.notest.gd @@ -0,0 +1,2 @@ +class APrime: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_b.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_b.notest.gd new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_b.notest.gd diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_base.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_base.notest.gd new file mode 100644 index 0000000000..666b147ced --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_base.notest.gd @@ -0,0 +1,4 @@ +const A: = preload("base_outer_resolution_a.notest.gd") + +class InnerClassInBase: + const C: = preload("base_outer_resolution_c.notest.gd") diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_c.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_c.notest.gd new file mode 100644 index 0000000000..814be35314 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_c.notest.gd @@ -0,0 +1,3 @@ +enum TestEnum { + HELLO_WORLD +} diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_extend.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_extend.notest.gd new file mode 100644 index 0000000000..fbd28779d4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_extend.notest.gd @@ -0,0 +1,23 @@ +extends "base_outer_resolution_base.notest.gd" + +const B: = preload("base_outer_resolution_b.notest.gd") + +static func test_a(a: A) -> void: + print(a is A) + +static func test_b(b: B) -> void: + print(b is B) + +class InnerClass extends InnerClassInBase: + static func test_c(c: C) -> void: + print(c is C) + + class InnerInnerClass: + static func test_a_b_c(a: A, b: B, c: C) -> void: + print(a is A and b is B and c is C) + + static func test_enum(test_enum: C.TestEnum) -> void: + print(test_enum == C.TestEnum.HELLO_WORLD) + + static func test_a_prime(a_prime: A.APrime) -> void: + print(a_prime is A.APrime) diff --git a/modules/gdscript/tests/scripts/analyzer/features/cast_non_null.gd b/modules/gdscript/tests/scripts/analyzer/features/cast_non_null.gd new file mode 100644 index 0000000000..ba1b198cbf --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/cast_non_null.gd @@ -0,0 +1,5 @@ +# https://github.com/godotengine/godot/issues/69504#issuecomment-1345725988 + +func test(): + print("cast to Variant == null: ", 1 as Variant == null) + print("cast to Object == null: ", self as Object == null) diff --git a/modules/gdscript/tests/scripts/analyzer/features/cast_non_null.out b/modules/gdscript/tests/scripts/analyzer/features/cast_non_null.out new file mode 100644 index 0000000000..541de99b8e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/cast_non_null.out @@ -0,0 +1,3 @@ +GDTEST_OK +cast to Variant == null: false +cast to Object == null: false diff --git a/modules/gdscript/tests/scripts/analyzer/features/class_from_parent.gd b/modules/gdscript/tests/scripts/analyzer/features/class_from_parent.gd index 30e7deb05a..7c846c59bd 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/class_from_parent.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/class_from_parent.gd @@ -1,19 +1,19 @@ class A: - var x = 3 + var x = 3 class B: - var x = 4 + var x = 4 class C: - var x = 5 + var x = 5 class Test: - var a = A.new() - var b: B = B.new() - var c := C.new() + 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) + 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/default_arg_convertable.gd b/modules/gdscript/tests/scripts/analyzer/features/default_arg_convertable.gd new file mode 100644 index 0000000000..d0d04897e0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/default_arg_convertable.gd @@ -0,0 +1,6 @@ +func check(arg: float = 3): + return typeof(arg) == typeof(3.0) + +func test(): + if check(): + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/default_arg_convertable.out b/modules/gdscript/tests/scripts/analyzer/features/default_arg_convertable.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/default_arg_convertable.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_access_types.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_access_types.gd new file mode 100644 index 0000000000..9bc08f2dc5 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_access_types.gd @@ -0,0 +1,29 @@ +class_name EnumAccessOuterClass + +class InnerClass: + enum MyEnum { V0, V2, V1 } + + static func print_enums(): + print("Inner - Inner") + print(MyEnum.V0, MyEnum.V1, MyEnum.V2) + print(InnerClass.MyEnum.V0, InnerClass.MyEnum.V1, InnerClass.MyEnum.V2) + print(EnumAccessOuterClass.InnerClass.MyEnum.V0, EnumAccessOuterClass.InnerClass.MyEnum.V1, EnumAccessOuterClass.InnerClass.MyEnum.V2) + + print("Inner - Outer") + print(EnumAccessOuterClass.MyEnum.V0, EnumAccessOuterClass.MyEnum.V1, EnumAccessOuterClass.MyEnum.V2) + + +enum MyEnum { V0, V1, V2 } + +func print_enums(): + print("Outer - Outer") + print(MyEnum.V0, MyEnum.V1, MyEnum.V2) + print(EnumAccessOuterClass.MyEnum.V0, EnumAccessOuterClass.MyEnum.V1, EnumAccessOuterClass.MyEnum.V2) + + print("Outer - Inner") + print(InnerClass.MyEnum.V0, InnerClass.MyEnum.V1, InnerClass.MyEnum.V2) + print(EnumAccessOuterClass.InnerClass.MyEnum.V0, EnumAccessOuterClass.InnerClass.MyEnum.V1, EnumAccessOuterClass.InnerClass.MyEnum.V2) + +func test(): + print_enums() + InnerClass.print_enums() diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_access_types.out b/modules/gdscript/tests/scripts/analyzer/features/enum_access_types.out new file mode 100644 index 0000000000..02e2e2b396 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_access_types.out @@ -0,0 +1,13 @@ +GDTEST_OK +Outer - Outer +012 +012 +Outer - Inner +021 +021 +Inner - Inner +021 +021 +021 +Inner - Outer +012 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_as_const.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_as_const.gd new file mode 100644 index 0000000000..2ce588373b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_as_const.gd @@ -0,0 +1,29 @@ +class Outer: + enum OuterEnum { OuterValue = 3 } + const OuterConst := OuterEnum + + class Inner: + enum InnerEnum { InnerValue = 7 } + const InnerConst := InnerEnum + + static func test() -> void: + print(OuterEnum.size()); + print(OuterEnum.OuterValue); + print(OuterConst.size()); + print(OuterConst.OuterValue); + print(Outer.OuterEnum.size()); + print(Outer.OuterEnum.OuterValue); + print(Outer.OuterConst.size()); + print(Outer.OuterConst.OuterValue); + + print(InnerEnum.size()); + print(InnerEnum.InnerValue); + print(InnerConst.size()); + print(InnerConst.InnerValue); + print(Inner.InnerEnum.size()); + print(Inner.InnerEnum.InnerValue); + print(Inner.InnerConst.size()); + print(Inner.InnerConst.InnerValue); + +func test(): + Outer.Inner.test() diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_as_const.out b/modules/gdscript/tests/scripts/analyzer/features/enum_as_const.out new file mode 100644 index 0000000000..e049f85b6e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_as_const.out @@ -0,0 +1,17 @@ +GDTEST_OK +1 +3 +1 +3 +1 +3 +1 +3 +1 +7 +1 +7 +1 +7 +1 +7 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_duplicate_into_dict.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_duplicate_into_dict.gd new file mode 100644 index 0000000000..3076e7069f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_duplicate_into_dict.gd @@ -0,0 +1,13 @@ +enum Enum {V1, V2} + +func test(): + var enumAsDict : Dictionary = Enum.duplicate() + var enumAsVariant = Enum.duplicate() + print(Enum.has("V1")) + print(enumAsDict.has("V1")) + print(enumAsVariant.has("V1")) + enumAsDict.clear() + enumAsVariant.clear() + print(Enum.has("V1")) + print(enumAsDict.has("V1")) + print(enumAsVariant.has("V1")) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_duplicate_into_dict.out b/modules/gdscript/tests/scripts/analyzer/features/enum_duplicate_into_dict.out new file mode 100644 index 0000000000..a41924d0c9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_duplicate_into_dict.out @@ -0,0 +1,7 @@ +GDTEST_OK +true +true +true +true +false +false diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_from_base.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_from_base.gd new file mode 100644 index 0000000000..b3f9941903 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_from_base.gd @@ -0,0 +1,13 @@ +class A: + enum Named { VALUE_A, VALUE_B, VALUE_C = 42 } + +class B extends A: + var a = Named.VALUE_A + var b = Named.VALUE_B + var c = Named.VALUE_C + +func test(): + var test_instance = B.new() + prints("a", test_instance.a, test_instance.a == A.Named.VALUE_A) + prints("b", test_instance.b, test_instance.b == A.Named.VALUE_B) + prints("c", test_instance.c, test_instance.c == B.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_base.out index c160839da3..c160839da3 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_from_base.out diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_from_outer.gd index 5f57c5b8c2..4d6852a9be 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_from_outer.gd @@ -1,5 +1,3 @@ -extends Node - enum Named { VALUE_A, VALUE_B, VALUE_C = 42 } class Test: diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_from_outer.out b/modules/gdscript/tests/scripts/analyzer/features/enum_from_outer.out new file mode 100644 index 0000000000..c160839da3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_from_outer.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_function_typecheck.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.gd new file mode 100644 index 0000000000..8a4e89d0d6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.gd @@ -0,0 +1,112 @@ +class_name EnumFunctionTypecheckOuterClass + +enum MyEnum { V0, V1, V2 } + +class InnerClass: + enum MyEnum { V0, V2, V1 } + + func inner_inner_no_class(e : MyEnum) -> MyEnum: + print(e) + return e + + func inner_inner_class(e : InnerClass.MyEnum) -> InnerClass.MyEnum: + print(e) + return e + + func inner_inner_class_class(e : EnumFunctionTypecheckOuterClass.InnerClass.MyEnum) -> EnumFunctionTypecheckOuterClass.InnerClass.MyEnum: + print(e) + return e + + func inner_outer(e : EnumFunctionTypecheckOuterClass.MyEnum) -> EnumFunctionTypecheckOuterClass.MyEnum: + print(e) + return e + + func test(): + var _d + print("Inner") + + var o := EnumFunctionTypecheckOuterClass.new() + + _d = o.outer_outer_no_class(EnumFunctionTypecheckOuterClass.MyEnum.V1) + print() + _d = o.outer_outer_class(EnumFunctionTypecheckOuterClass.MyEnum.V1) + print() + _d = o.outer_inner_class(MyEnum.V1) + _d = o.outer_inner_class(InnerClass.MyEnum.V1) + _d = o.outer_inner_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1) + print() + _d = o.outer_inner_class_class(MyEnum.V1) + _d = o.outer_inner_class_class(InnerClass.MyEnum.V1) + _d = o.outer_inner_class_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1) + print() + print() + + + _d = inner_inner_no_class(MyEnum.V1) + _d = inner_inner_no_class(InnerClass.MyEnum.V1) + _d = inner_inner_no_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1) + print() + _d = inner_inner_class(MyEnum.V1) + _d = inner_inner_class(InnerClass.MyEnum.V1) + _d = inner_inner_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1) + print() + _d = inner_inner_class_class(MyEnum.V1) + _d = inner_inner_class_class(InnerClass.MyEnum.V1) + _d = inner_inner_class_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1) + print() + _d = inner_outer(EnumFunctionTypecheckOuterClass.MyEnum.V1) + print() + print() + + +func outer_outer_no_class(e : MyEnum) -> MyEnum: + print(e) + return e + +func outer_outer_class(e : EnumFunctionTypecheckOuterClass.MyEnum) -> EnumFunctionTypecheckOuterClass.MyEnum: + print(e) + return e + +func outer_inner_class(e : InnerClass.MyEnum) -> InnerClass.MyEnum: + print(e) + return e + +func outer_inner_class_class(e : EnumFunctionTypecheckOuterClass.InnerClass.MyEnum) -> EnumFunctionTypecheckOuterClass.InnerClass.MyEnum: + print(e) + return e + +func test(): + var _d + print("Outer") + + _d = outer_outer_no_class(MyEnum.V1) + _d = outer_outer_no_class(EnumFunctionTypecheckOuterClass.MyEnum.V1) + print() + _d = outer_outer_class(MyEnum.V1) + _d = outer_outer_class(EnumFunctionTypecheckOuterClass.MyEnum.V1) + print() + _d = outer_inner_class(InnerClass.MyEnum.V1) + _d = outer_inner_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1) + print() + _d = outer_inner_class_class(InnerClass.MyEnum.V1) + _d = outer_inner_class_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1) + print() + print() + + var i := EnumFunctionTypecheckOuterClass.InnerClass.new() + + _d = i.inner_inner_no_class(InnerClass.MyEnum.V1) + _d = i.inner_inner_no_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1) + print() + _d = i.inner_inner_class(InnerClass.MyEnum.V1) + _d = i.inner_inner_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1) + print() + _d = i.inner_inner_class_class(InnerClass.MyEnum.V1) + _d = i.inner_inner_class_class(EnumFunctionTypecheckOuterClass.InnerClass.MyEnum.V1) + print() + _d = i.inner_outer(MyEnum.V1) + _d = i.inner_outer(EnumFunctionTypecheckOuterClass.MyEnum.V1) + print() + print() + + i.test() diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.out b/modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.out new file mode 100644 index 0000000000..2e3ce1aa10 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_function_typecheck.out @@ -0,0 +1,55 @@ +GDTEST_OK +Outer +1 +1 + +1 +1 + +2 +2 + +2 +2 + + +2 +2 + +2 +2 + +2 +2 + +1 +1 + + +Inner +1 + +1 + +2 +2 +2 + +2 +2 +2 + + +2 +2 +2 + +2 +2 +2 + +2 +2 +2 + +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_named_no_shadow.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_named_no_shadow.gd new file mode 100644 index 0000000000..b97d9bbb6b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_named_no_shadow.gd @@ -0,0 +1,16 @@ +const A := 1 +enum { B } +enum NamedEnum { C } + +class Parent: + const D := 2 + enum { E } + enum NamedEnum2 { F } + +class Child extends Parent: + enum TestEnum { A, B, C, D, E, F, Node, Object, Child, Parent} + +func test(): + print(A, B, NamedEnum.C, Parent.D, Parent.E, Parent.NamedEnum2.F) + print(Child.TestEnum.A, Child.TestEnum.B, Child.TestEnum.C, Child.TestEnum.D, Child.TestEnum.E, Child.TestEnum.F) + print(Child.TestEnum.Node, Child.TestEnum.Object, Child.TestEnum.Child, Child.TestEnum.Parent) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_named_no_shadow.out b/modules/gdscript/tests/scripts/analyzer/features/enum_named_no_shadow.out new file mode 100644 index 0000000000..864ba2a549 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_named_no_shadow.out @@ -0,0 +1,4 @@ +GDTEST_OK +100200 +012345 +6789 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_native_access_types.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_native_access_types.gd new file mode 100644 index 0000000000..6a0a1e1969 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_native_access_types.gd @@ -0,0 +1,19 @@ +func print_enum(e : TileSet.TileShape) -> TileSet.TileShape: + print(e) + return e + +func test(): + var v : TileSet.TileShape + v = TileSet.TILE_SHAPE_SQUARE + v = print_enum(v) + v = print_enum(TileSet.TILE_SHAPE_SQUARE) + v = TileSet.TileShape.TILE_SHAPE_SQUARE + v = print_enum(v) + v = print_enum(TileSet.TileShape.TILE_SHAPE_SQUARE) + + v = TileSet.TILE_SHAPE_ISOMETRIC + v = print_enum(v) + v = print_enum(TileSet.TILE_SHAPE_ISOMETRIC) + v = TileSet.TileShape.TILE_SHAPE_ISOMETRIC + v = print_enum(v) + v = print_enum(TileSet.TileShape.TILE_SHAPE_ISOMETRIC) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_native_access_types.out b/modules/gdscript/tests/scripts/analyzer/features/enum_native_access_types.out new file mode 100644 index 0000000000..1126dcc6ec --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_native_access_types.out @@ -0,0 +1,9 @@ +GDTEST_OK +0 +0 +0 +0 +1 +1 +1 +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.gd new file mode 100644 index 0000000000..b05ae82048 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.gd @@ -0,0 +1,86 @@ +class_name EnumTypecheckOuterClass + +enum MyEnum { V0, V1, V2 } + +class InnerClass: + enum MyEnum { V0, V2, V1 } + + static func test_inner_from_inner(): + print("Inner - Inner") + var e1 : MyEnum + var e2 : InnerClass.MyEnum + var e3 : EnumTypecheckOuterClass.InnerClass.MyEnum + + print("Self ", e1, e2, e3) + e1 = MyEnum.V1 + e2 = MyEnum.V1 + e3 = MyEnum.V1 + print("MyEnum ", e1, e2, e3) + e1 = InnerClass.MyEnum.V1 + e2 = InnerClass.MyEnum.V1 + e3 = InnerClass.MyEnum.V1 + print("Inner.MyEnum ", e1, e2, e3) + e1 = EnumTypecheckOuterClass.InnerClass.MyEnum.V1 + e2 = EnumTypecheckOuterClass.InnerClass.MyEnum.V1 + e3 = EnumTypecheckOuterClass.InnerClass.MyEnum.V1 + print("Outer.Inner.MyEnum ", e1, e2, e3) + + e1 = e2 + e1 = e3 + e2 = e1 + e2 = e3 + e3 = e1 + e3 = e2 + + print() + + static func test_outer_from_inner(): + print("Inner - Outer") + var e : EnumTypecheckOuterClass.MyEnum + + e = EnumTypecheckOuterClass.MyEnum.V1 + print("Outer.MyEnum ", e) + + print() + +func test_outer_from_outer(): + print("Outer - Outer") + var e1 : MyEnum + var e2 : EnumTypecheckOuterClass.MyEnum + + print("Self ", e1, e2) + e1 = MyEnum.V1 + e2 = MyEnum.V1 + print("Outer ", e1, e2) + e1 = EnumTypecheckOuterClass.MyEnum.V1 + e2 = EnumTypecheckOuterClass.MyEnum.V1 + print("Outer.MyEnum ", e1, e2) + + e1 = e2 + e2 = e1 + + print() + +func test_inner_from_outer(): + print("Outer - Inner") + var e1 : InnerClass.MyEnum + var e2 : EnumTypecheckOuterClass.InnerClass.MyEnum + + print("Inner ", e1, e2) + e1 = InnerClass.MyEnum.V1 + e2 = InnerClass.MyEnum.V1 + print("Outer.Inner ", e1, e2) + e1 = EnumTypecheckOuterClass.InnerClass.MyEnum.V1 + e2 = EnumTypecheckOuterClass.InnerClass.MyEnum.V1 + print("Outer.Inner.MyEnum ", e1, e2) + + e1 = e2 + e2 = e1 + + print() + +func test(): + test_outer_from_outer() + test_inner_from_outer() + InnerClass.test_outer_from_inner() + InnerClass.test_inner_from_inner() diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.out b/modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.out new file mode 100644 index 0000000000..3b2dcade26 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_typecheck_inner_class.out @@ -0,0 +1,19 @@ +GDTEST_OK +Outer - Outer +Self 00 +Outer 11 +Outer.MyEnum 11 + +Outer - Inner +Inner 00 +Outer.Inner 22 +Outer.Inner.MyEnum 22 + +Inner - Outer +Outer.MyEnum 1 + +Inner - Inner +Self 000 +MyEnum 222 +Inner.MyEnum 222 +Outer.Inner.MyEnum 222 diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_unnamed_depend.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_unnamed_depend.gd new file mode 100644 index 0000000000..f351fc1f7b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_unnamed_depend.gd @@ -0,0 +1,7 @@ +enum { + V1, + V2 = V1, +} + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_unnamed_depend.out b/modules/gdscript/tests/scripts/analyzer/features/enum_unnamed_depend.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_unnamed_depend.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/extend_abstract_class.gd b/modules/gdscript/tests/scripts/analyzer/features/extend_abstract_class.gd new file mode 100644 index 0000000000..95c3268130 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/extend_abstract_class.gd @@ -0,0 +1,12 @@ +class A extends CanvasItem: + func _init(): + pass + +class B extends A: + pass + +class C extends CanvasItem: + pass + +func test(): + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/extend_abstract_class.out b/modules/gdscript/tests/scripts/analyzer/features/extend_abstract_class.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/extend_abstract_class.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_enum_as_constant.gd b/modules/gdscript/tests/scripts/analyzer/features/external_enum_as_constant.gd new file mode 100644 index 0000000000..0c740935b9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_enum_as_constant.gd @@ -0,0 +1,6 @@ +const External = preload("external_enum_as_constant_external.notest.gd") +const MyEnum = External.MyEnum + +func test(): + print(MyEnum.WAITING == 0) + print(MyEnum.GODOT == 1) diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_enum_as_constant.out b/modules/gdscript/tests/scripts/analyzer/features/external_enum_as_constant.out new file mode 100644 index 0000000000..9d111a8322 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_enum_as_constant.out @@ -0,0 +1,3 @@ +GDTEST_OK +true +true diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_enum_as_constant_external.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_enum_as_constant_external.notest.gd new file mode 100644 index 0000000000..24c1e41aab --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_enum_as_constant_external.notest.gd @@ -0,0 +1,4 @@ +enum MyEnum { + WAITING, + GODOT +} diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.gd b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.gd new file mode 100644 index 0000000000..3f9bfe189c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.gd @@ -0,0 +1,4 @@ +extends "inner_base.gd".InnerA.InnerAB + +func test(): + super.test() diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.out b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.out new file mode 100644 index 0000000000..62f1383392 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.out @@ -0,0 +1,3 @@ +GDTEST_OK +InnerA.InnerAB.test +InnerB.test diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.gd b/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.gd new file mode 100644 index 0000000000..18dca109fb --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.gd @@ -0,0 +1,7 @@ +const External = preload("external_inner_class_as_constant_external.notest.gd") +const ExternalInnerClass = External.InnerClass + +func test(): + var inst_external: ExternalInnerClass = ExternalInnerClass.new() + inst_external.x = 4.0 + print(inst_external.x) diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out b/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out new file mode 100644 index 0000000000..15666c46ad --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant.out @@ -0,0 +1,2 @@ +GDTEST_OK +4 diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant_external.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant_external.notest.gd new file mode 100644 index 0000000000..788c99d469 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_inner_class_as_constant_external.notest.gd @@ -0,0 +1,2 @@ +class InnerClass: + var x: = 3.0 diff --git a/modules/gdscript/tests/scripts/analyzer/features/for_loop_on_variant.gd b/modules/gdscript/tests/scripts/analyzer/features/for_loop_on_variant.gd new file mode 100644 index 0000000000..7b74be6f2c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/for_loop_on_variant.gd @@ -0,0 +1,15 @@ +func test(): + var variant_int: Variant = 1 + var weak_int = 1 + + for x in variant_int: + if x is String: + print('never') + print(x) + + for x in weak_int: + if x is String: + print('never') + print(x) + + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/for_loop_on_variant.out b/modules/gdscript/tests/scripts/analyzer/features/for_loop_on_variant.out new file mode 100644 index 0000000000..7677671cfd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/for_loop_on_variant.out @@ -0,0 +1,4 @@ +GDTEST_OK +0 +0 +ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.gd b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.notest.gd index ea744e3027..c3fc176679 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.notest.gd @@ -1,7 +1,4 @@ const A := 42 -func test(): - pass - func something(): return "OK" diff --git a/modules/gdscript/tests/scripts/analyzer/features/inferred_return_type.gd b/modules/gdscript/tests/scripts/analyzer/features/inferred_return_type.gd new file mode 100644 index 0000000000..39ced354df --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/inferred_return_type.gd @@ -0,0 +1,9 @@ +# https://github.com/godotengine/godot/issues/61159 + +func get_param(): + return null + +func test(): + var v = get_param() + v = get_param() + print(v) diff --git a/modules/gdscript/tests/scripts/analyzer/features/inferred_return_type.out b/modules/gdscript/tests/scripts/analyzer/features/inferred_return_type.out new file mode 100644 index 0000000000..f0c83a69b3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/inferred_return_type.out @@ -0,0 +1,2 @@ +GDTEST_OK +<null> diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_base.gd b/modules/gdscript/tests/scripts/analyzer/features/inner_base.gd new file mode 100644 index 0000000000..a825b59255 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/inner_base.gd @@ -0,0 +1,18 @@ +extends InnerA + +func test(): + super.test() + +class InnerA extends InnerAB: + func test(): + print("InnerA.test") + super.test() + + class InnerAB extends InnerB: + func test(): + print("InnerA.InnerAB.test") + super.test() + +class InnerB: + func test(): + print("InnerB.test") diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_base.out b/modules/gdscript/tests/scripts/analyzer/features/inner_base.out new file mode 100644 index 0000000000..ddd5ffcfd3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/inner_base.out @@ -0,0 +1,4 @@ +GDTEST_OK +InnerA.test +InnerA.InnerAB.test +InnerB.test diff --git a/modules/gdscript/tests/scripts/analyzer/features/lambda_typed.gd b/modules/gdscript/tests/scripts/analyzer/features/lambda_typed.gd new file mode 100644 index 0000000000..114d7f7652 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/lambda_typed.gd @@ -0,0 +1,12 @@ +func test(): + var lambda_0 := func() -> void: + print(0) + lambda_0.call() + + var lambda_1 := func(printed: int) -> void: + print(printed) + lambda_1.call(1) + + var lambda_2 := func(identity: int) -> int: + return identity + print(lambda_2.call(2)) diff --git a/modules/gdscript/tests/scripts/analyzer/features/lambda_typed.out b/modules/gdscript/tests/scripts/analyzer/features/lambda_typed.out new file mode 100644 index 0000000000..63e262aeff --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/lambda_typed.out @@ -0,0 +1,4 @@ +GDTEST_OK +0 +1 +2 diff --git a/modules/gdscript/tests/scripts/analyzer/features/lookup_class.gd b/modules/gdscript/tests/scripts/analyzer/features/lookup_class.gd new file mode 100644 index 0000000000..541da78332 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/lookup_class.gd @@ -0,0 +1,50 @@ +# Inner-outer class lookup +class A: + const Q: = "right one" + +class X: + const Q: = "wrong one" + +class Y extends X: + class B extends A: + static func check() -> void: + print(Q) + +# External class lookup +const External: = preload("lookup_class_external.notest.gd") + +class Internal extends External.A: + static func check() -> void: + print(TARGET) + + class E extends External.E: + static func check() -> void: + print(TARGET) + print(WAITING) + +# Variable lookup +class C: + var Q := 'right one' + +class D: + const Q := 'wrong one' + +class E extends D: + class F extends C: + func check() -> void: + print(Q) + +# Test +func test() -> void: + # Inner-outer class lookup + Y.B.check() + print("---") + + # External class lookup + Internal.check() + Internal.E.check() + print("---") + + # Variable lookup + var f: = E.F.new() + f.check() diff --git a/modules/gdscript/tests/scripts/analyzer/features/lookup_class.out b/modules/gdscript/tests/scripts/analyzer/features/lookup_class.out new file mode 100644 index 0000000000..a0983c1438 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/lookup_class.out @@ -0,0 +1,8 @@ +GDTEST_OK +right one +--- +wrong +right +godot +--- +right one diff --git a/modules/gdscript/tests/scripts/analyzer/features/lookup_class_external.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/lookup_class_external.notest.gd new file mode 100644 index 0000000000..a2904e20a8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/lookup_class_external.notest.gd @@ -0,0 +1,15 @@ +class A: + const TARGET: = "wrong" + + class B: + const TARGET: = "wrong" + const WAITING: = "godot" + + class D extends C: + pass + +class C: + const TARGET: = "right" + +class E extends A.B.D: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/lookup_signal.gd b/modules/gdscript/tests/scripts/analyzer/features/lookup_signal.gd new file mode 100644 index 0000000000..26cf6c7322 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/lookup_signal.gd @@ -0,0 +1,41 @@ +signal hello + +func get_signal() -> Signal: + return hello + +class A: + signal hello + + func get_signal() -> Signal: + return hello + + class B: + signal hello + + func get_signal() -> Signal: + return hello + +class C extends A.B: + func get_signal() -> Signal: + return hello + +func test(): + var a: = A.new() + var b: = A.B.new() + var c: = C.new() + + var hello_a_result: = hello == a.get_signal() + var hello_b_result: = hello == b.get_signal() + var hello_c_result: = hello == c.get_signal() + var a_b_result: = a.get_signal() == b.get_signal() + var a_c_result: = a.get_signal() == c.get_signal() + var b_c_result: = b.get_signal() == c.get_signal() + var c_c_result: = c.get_signal() == c.get_signal() + + print("hello == A.hello? %s" % hello_a_result) + print("hello == A.B.hello? %s" % hello_b_result) + print("hello == C.hello? %s" % hello_c_result) + print("A.hello == A.B.hello? %s" % a_b_result) + print("A.hello == C.hello? %s" % a_c_result) + print("A.B.hello == C.hello? %s" % b_c_result) + print("C.hello == C.hello? %s" % c_c_result) diff --git a/modules/gdscript/tests/scripts/analyzer/features/lookup_signal.out b/modules/gdscript/tests/scripts/analyzer/features/lookup_signal.out new file mode 100644 index 0000000000..6b0d32eaf8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/lookup_signal.out @@ -0,0 +1,8 @@ +GDTEST_OK +hello == A.hello? false +hello == A.B.hello? false +hello == C.hello? false +A.hello == A.B.hello? false +A.hello == C.hello? false +A.B.hello == C.hello? false +C.hello == C.hello? true diff --git a/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd new file mode 100644 index 0000000000..5a413e2015 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.gd @@ -0,0 +1,32 @@ +func check(input: int) -> bool: + return input == 1 + +var recur = null +var prop = null + +func check_arg(arg = null) -> void: + if arg != null: + print(check(arg)) + +func check_recur() -> void: + if recur != null: + print(check(recur)) + else: + recur = 1 + check_recur() + +func test() -> void: + check_arg(1) + + check_recur() + + if prop == null: + set('prop', 1) + print(check(prop)) + set('prop', null) + + var loop = null + while loop != 2: + if loop != null: + print(check(loop)) + loop = 1 if loop == null else 2 diff --git a/modules/gdscript/tests/scripts/analyzer/features/null_initializer.out b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.out new file mode 100644 index 0000000000..f9783e4362 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/null_initializer.out @@ -0,0 +1,5 @@ +GDTEST_OK +true +true +true +true diff --git a/modules/gdscript/tests/scripts/analyzer/features/out_of_order.gd b/modules/gdscript/tests/scripts/analyzer/features/out_of_order.gd new file mode 100644 index 0000000000..11349cc916 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/out_of_order.gd @@ -0,0 +1,51 @@ +func test(): + print("v1: ", v1) + print("v1 is String: ", v1 is String) + print("v2: ", v2) + print("v2 is bool: ", v2 is bool) + print("c1: ", c1) + print("c1 is int: ", c1 is int) + print("c2: ", c2) + print("c2 is int: ", c2 is int) + print("E1.V1: ", E1.V1) + print("E1.V2: ", E1.V2) + print("E2.V: ", E2.V) + print("EV1: ", EV1) + print("EV2: ", EV2) + print("EV3: ", EV3) + +var v1 := InnerA.new().fn() + +class InnerA extends InnerAB: + func fn(p2 := E1.V2) -> String: + return "%s, p2=%s" % [super.fn(), p2] + + class InnerAB: + func fn(p1 := c1) -> String: + return "p1=%s" % p1 + +var v2 := f() + +func f() -> bool: + return true + +const c1 := E1.V1 + +enum E1 { + V1 = E2.V + 2, + V2 = V1 - 1 +} + +enum E2 {V = 2} + +const c2 := EV2 + +enum { + EV1 = 42, + UNUSED = EV3, + EV2 +} + +enum { + EV3 = EV1 + 1 +} diff --git a/modules/gdscript/tests/scripts/analyzer/features/out_of_order.out b/modules/gdscript/tests/scripts/analyzer/features/out_of_order.out new file mode 100644 index 0000000000..b1e75d611d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/out_of_order.out @@ -0,0 +1,15 @@ +GDTEST_OK +v1: p1=4, p2=3 +v1 is String: true +v2: true +v2 is bool: true +c1: 4 +c1 is int: true +c2: 44 +c2 is int: true +E1.V1: 4 +E1.V2: 3 +E2.V: 2 +EV1: 42 +EV2: 44 +EV3: 43 diff --git a/modules/gdscript/tests/scripts/analyzer/features/out_of_order_external.gd b/modules/gdscript/tests/scripts/analyzer/features/out_of_order_external.gd new file mode 100644 index 0000000000..0b162bdff8 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/out_of_order_external.gd @@ -0,0 +1,39 @@ +const B = preload("out_of_order_external_a.notest.gd") + +func test(): + print("v1: ", v1) + print("v1 is String: ", v1 is String) + print("v2: ", v2) + print("v2 is bool: ", v2 is bool) + print("c1: ", c1) + print("c1 is int: ", c1 is int) + print("c2: ", c2) + print("c2 is int: ", c2 is int) + print("E1.V1: ", E1.V1) + print("E1.V2: ", E1.V2) + print("B.E2.V: ", B.E2.V) + print("EV1: ", EV1) + print("EV2: ", EV2) + print("B.EV3: ", B.EV3) + +var v1 := Inner.new().fn() + +class Inner extends B.Inner: + func fn(p2 := E1.V2) -> String: + return "%s, p2=%s" % [super.fn(), p2] + +var v2 := B.new().f() + +const c1 := E1.V1 + +enum E1 { + V1 = B.E2.V + 2, + V2 = V1 - 1 +} + +const c2 := EV2 + +enum { + EV1 = 42, + EV2 = B.EV3 + 1 +} diff --git a/modules/gdscript/tests/scripts/analyzer/features/out_of_order_external.out b/modules/gdscript/tests/scripts/analyzer/features/out_of_order_external.out new file mode 100644 index 0000000000..437f782fe6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/out_of_order_external.out @@ -0,0 +1,15 @@ +GDTEST_OK +v1: p1=4, p2=3 +v1 is String: true +v2: true +v2 is bool: true +c1: 4 +c1 is int: true +c2: 44 +c2 is int: true +E1.V1: 4 +E1.V2: 3 +B.E2.V: 2 +EV1: 42 +EV2: 44 +B.EV3: 43 diff --git a/modules/gdscript/tests/scripts/analyzer/features/out_of_order_external_a.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/out_of_order_external_a.notest.gd new file mode 100644 index 0000000000..d276f72fcf --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/out_of_order_external_a.notest.gd @@ -0,0 +1,12 @@ +const A = preload("out_of_order_external.gd") + +class Inner: + func fn(p1 := A.c1) -> String: + return "p1=%s" % p1 + +func f(p := A.c1) -> bool: + return p is int + +enum E2 {V = 2} + +enum {EV3 = A.EV1 + 1} diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd index 276875dd5a..9d0324ead8 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd @@ -1,4 +1,4 @@ -const Constants = preload("gdscript_to_preload.gd") +const Constants = preload("gdscript_to_preload.notest.gd") func test(): var a := Constants.A diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.gd new file mode 100644 index 0000000000..b730453a8a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.gd @@ -0,0 +1,4 @@ +const A = preload("preload_cyclic_reference_a.notest.gd") + +func test(): + A.test_cyclic_reference() diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.out b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.out new file mode 100644 index 0000000000..14bb971221 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.out @@ -0,0 +1,2 @@ +GDTEST_OK +godot diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_a.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_a.notest.gd new file mode 100644 index 0000000000..7a6035ded1 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_a.notest.gd @@ -0,0 +1,12 @@ +const B = preload("preload_cyclic_reference_b.notest.gd") + +const WAITING_FOR = "godot" + +static func test_cyclic_reference(): + B.test_cyclic_reference() + +static func test_cyclic_reference_2(): + B.test_cyclic_reference_2() + +static func test_cyclic_reference_3(): + B.test_cyclic_reference_3() diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_b.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_b.notest.gd new file mode 100644 index 0000000000..3ea5b01156 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_b.notest.gd @@ -0,0 +1,10 @@ +const A = preload("preload_cyclic_reference_a.notest.gd") + +static func test_cyclic_reference(): + A.test_cyclic_reference_2() + +static func test_cyclic_reference_2(): + A.test_cyclic_reference_3() + +static func test_cyclic_reference_3(): + print(A.WAITING_FOR) diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_script_native_type.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_script_native_type.gd new file mode 100644 index 0000000000..25381035b2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_script_native_type.gd @@ -0,0 +1,15 @@ +const Preloaded := preload( 'preload_script_native_type.notest.gd' ) + +func test() -> void: + var inferred := Preloaded.new() + var inferred_owner := inferred.owner + + var typed: Preloaded + typed = Preloaded.new() + var typed_owner := typed.owner + + print(typed_owner == inferred_owner) + + inferred.free() + typed.free() + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_script_native_type.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_script_native_type.notest.gd new file mode 100644 index 0000000000..61510e14cd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_script_native_type.notest.gd @@ -0,0 +1 @@ +extends Node diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_script_native_type.out b/modules/gdscript/tests/scripts/analyzer/features/preload_script_native_type.out new file mode 100644 index 0000000000..3e24a1e2af --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_script_native_type.out @@ -0,0 +1,3 @@ +GDTEST_OK +true +ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/return_variant_typed.gd b/modules/gdscript/tests/scripts/analyzer/features/return_variant_typed.gd new file mode 100644 index 0000000000..95f04421d1 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/return_variant_typed.gd @@ -0,0 +1,5 @@ +func variant() -> Variant: + return 'variant' + +func test(): + print(variant()) diff --git a/modules/gdscript/tests/scripts/analyzer/features/return_variant_typed.out b/modules/gdscript/tests/scripts/analyzer/features/return_variant_typed.out new file mode 100644 index 0000000000..57fe608cc5 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/return_variant_typed.out @@ -0,0 +1,2 @@ +GDTEST_OK +variant diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_as_default_parameter.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_as_default_parameter.gd new file mode 100644 index 0000000000..fc343377fc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_as_default_parameter.gd @@ -0,0 +1,17 @@ +func print_untyped(array = [0]) -> void: + print(array) + print(array.get_typed_builtin()) + +func print_inferred(array := [1]) -> void: + print(array) + print(array.get_typed_builtin()) + +func print_typed(array: Array[int] = [2]) -> void: + print(array) + print(array.get_typed_builtin()) + +func test(): + print_untyped() + print_inferred() + print_typed() + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_as_default_parameter.out b/modules/gdscript/tests/scripts/analyzer/features/typed_array_as_default_parameter.out new file mode 100644 index 0000000000..082e3ade19 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_as_default_parameter.out @@ -0,0 +1,8 @@ +GDTEST_OK +[0] +0 +[1] +2 +[2] +2 +ok 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 index 5f73064cc0..beabf3d2e5 100644 --- 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 @@ -1,4 +1,4 @@ -const preloaded : GDScript = preload("gdscript_to_preload.gd") +const preloaded : GDScript = preload("gdscript_to_preload.notest.gd") func test(): var preloaded_instance: preloaded = preloaded.new() diff --git a/modules/gdscript/tests/scripts/analyzer/features/variant_arg_in_virtual_method.gd b/modules/gdscript/tests/scripts/analyzer/features/variant_arg_in_virtual_method.gd new file mode 100644 index 0000000000..da24c06b2e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/variant_arg_in_virtual_method.gd @@ -0,0 +1,6 @@ +class Check extends Node: + func _set(_property: StringName, _value: Variant) -> bool: + return true + +func test() -> void: + print('OK') diff --git a/modules/gdscript/tests/scripts/analyzer/features/variant_arg_in_virtual_method.out b/modules/gdscript/tests/scripts/analyzer/features/variant_arg_in_virtual_method.out new file mode 100644 index 0000000000..1ccb591560 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/variant_arg_in_virtual_method.out @@ -0,0 +1,2 @@ +GDTEST_OK +OK diff --git a/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd b/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd index 877a4ea221..4c02fd4b0d 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd @@ -1,12 +1,12 @@ -@warning_ignore(unused_private_class_variable) +@warning_ignore("unused_private_class_variable") var _unused = 2 -@warning_ignore(unused_variable) +@warning_ignore("unused_variable") func test(): print("test") var unused = 3 - @warning_ignore(redundant_await) + @warning_ignore("redundant_await") print(await regular_func()) print("done") diff --git a/modules/gdscript/tests/scripts/analyzer/features/weak_initializer.gd b/modules/gdscript/tests/scripts/analyzer/features/weak_initializer.gd new file mode 100644 index 0000000000..c5f3ccc59e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/weak_initializer.gd @@ -0,0 +1,5 @@ +func test(): + var bar = 1 + var foo: float = bar + print(typeof(foo)) + print(foo is float) diff --git a/modules/gdscript/tests/scripts/analyzer/features/weak_initializer.out b/modules/gdscript/tests/scripts/analyzer/features/weak_initializer.out new file mode 100644 index 0000000000..5d798c1f24 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/weak_initializer.out @@ -0,0 +1,3 @@ +GDTEST_OK +3 +true diff --git a/modules/gdscript/tests/scripts/analyzer/typed_array_assignment.out b/modules/gdscript/tests/scripts/analyzer/typed_array_assignment.out index 26b6e13d4f..ad2e6558d7 100644 --- a/modules/gdscript/tests/scripts/analyzer/typed_array_assignment.out +++ b/modules/gdscript/tests/scripts/analyzer/typed_array_assignment.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Assigned value for constant "arr" has type Array[String] which is not compatible with defined type Array[int]. +Cannot assign a value of type Array[String] to constant "arr" with specified type Array[int]. diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.gd b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.gd new file mode 100644 index 0000000000..939496324c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.gd @@ -0,0 +1,6 @@ +var shadow: int + +func test(): + var lambda := func(shadow: String) -> void: + print(shadow) + lambda.call('shadow') diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out new file mode 100644 index 0000000000..a98d80514c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_shadowing_arg.out @@ -0,0 +1,6 @@ +GDTEST_OK +>> WARNING +>> Line: 4 +>> SHADOWED_VARIABLE +>> The local function parameter "shadow" is shadowing an already-declared variable at line 1. +shadow diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.gd b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.gd new file mode 100644 index 0000000000..6fc90ea29c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.gd @@ -0,0 +1,4 @@ +func test(): + var lambda := func(unused: Variant) -> void: + pass + lambda.call() diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.out b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.out new file mode 100644 index 0000000000..32e230fc80 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 2 +>> UNUSED_PARAMETER +>> The parameter 'unused' is never used in the function ''. If this is intended, prefix it with an underscore: '_unused' 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 index ada6030132..0085b3f367 100644 --- a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd +++ b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.gd @@ -1,6 +1,6 @@ -# Error here. `class_name` should be used *before* annotations, not after (except @tool). -@icon("res://path/to/optional/icon.svg") +# Error here. Annotations should be used before `class_name`, not after. class_name HelloWorld +@icon("res://path/to/optional/icon.svg") func test(): - pass + 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 index 02b33c8692..5fcf1cbcad 100644 --- a/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out +++ b/modules/gdscript/tests/scripts/parser/errors/class_name_after_annotation.out @@ -1,2 +1,2 @@ GDTEST_PARSER_ERROR -"class_name" should be used before annotations (except @tool). +Annotation "@icon" must be at the top of the script, before "extends" and "class_name". diff --git a/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd index 92dfb2366d..816783f239 100644 --- a/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd +++ b/modules/gdscript/tests/scripts/parser/errors/double_dictionary_comma.gd @@ -1,2 +1,2 @@ func test(): - var dictionary = { hello = "world",, } + var dictionary = { hello = "world",, } diff --git a/modules/gdscript/tests/scripts/parser/errors/identifier_similar_to_keyword.gd b/modules/gdscript/tests/scripts/parser/errors/identifier_similar_to_keyword.gd new file mode 100644 index 0000000000..4b1f284070 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/identifier_similar_to_keyword.gd @@ -0,0 +1,3 @@ +func test(): + var аs # Using Cyrillic "а". + print(аs) diff --git a/modules/gdscript/tests/scripts/parser/errors/identifier_similar_to_keyword.out b/modules/gdscript/tests/scripts/parser/errors/identifier_similar_to_keyword.out new file mode 100644 index 0000000000..337dec2f4d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/identifier_similar_to_keyword.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Identifier "аs" is visually similar to the GDScript keyword "as" and thus not allowed. diff --git a/modules/gdscript/tests/scripts/parser/errors/match_multiple_variable_binds_in_branch.gd b/modules/gdscript/tests/scripts/parser/errors/match_multiple_variable_binds_in_branch.gd index 4608c778aa..7a745bd995 100644 --- a/modules/gdscript/tests/scripts/parser/errors/match_multiple_variable_binds_in_branch.gd +++ b/modules/gdscript/tests/scripts/parser/errors/match_multiple_variable_binds_in_branch.gd @@ -1,4 +1,4 @@ func test(): - match 1: - [[[var a]]], 2: - pass + match 1: + [[[var a]]], 2: + pass diff --git a/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd index 43b513045b..a7197bf68f 100644 --- a/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd +++ b/modules/gdscript/tests/scripts/parser/features/advanced_expression_matching.gd @@ -1,34 +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") + 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}]]) + 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/arrays_dictionaries_nested_const.gd b/modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd index cc78309ae4..a34cc26e67 100644 --- a/modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd +++ b/modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd @@ -1,6 +1,6 @@ # https://github.com/godotengine/godot/issues/50285 -@warning_ignore(unused_local_constant) +@warning_ignore("unused_local_constant") func test(): const CONST_INNER_DICTIONARY = { "key": true } const CONST_NESTED_DICTIONARY_OLD_WORKAROUND = { diff --git a/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd index 2b46f1e88a..c959c6c6af 100644 --- a/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd +++ b/modules/gdscript/tests/scripts/parser/features/basic_expression_matching.gd @@ -1,27 +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") + 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}) + 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/class_name.gd b/modules/gdscript/tests/scripts/parser/features/class_name.gd index 8bd188e247..19009e433d 100644 --- a/modules/gdscript/tests/scripts/parser/features/class_name.gd +++ b/modules/gdscript/tests/scripts/parser/features/class_name.gd @@ -1,5 +1,5 @@ -class_name HelloWorld @icon("res://path/to/optional/icon.svg") +class_name HelloWorld func test(): pass diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd index 1e072728fc..acf9ff2e21 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd +++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd @@ -5,7 +5,7 @@ @export var color: Color @export_color_no_alpha var color_no_alpha: Color -@export_node_path(Sprite2D, Sprite3D, Control, Node) var nodepath := ^"hello" +@export_node_path("Sprite2D", "Sprite3D", "Control", "Node") var nodepath := ^"hello" func test(): diff --git a/modules/gdscript/tests/scripts/parser/features/lambda_callable.gd b/modules/gdscript/tests/scripts/parser/features/lambda_callable.gd index c3b2506156..17d00bce3c 100644 --- a/modules/gdscript/tests/scripts/parser/features/lambda_callable.gd +++ b/modules/gdscript/tests/scripts/parser/features/lambda_callable.gd @@ -1,4 +1,4 @@ func test(): - var my_lambda = func(x): - print(x) - my_lambda.call("hello") + var my_lambda = func(x): + print(x) + my_lambda.call("hello") diff --git a/modules/gdscript/tests/scripts/parser/features/match.gd b/modules/gdscript/tests/scripts/parser/features/match.gd index 4d05490aa5..59b5ba2426 100644 --- a/modules/gdscript/tests/scripts/parser/features/match.gd +++ b/modules/gdscript/tests/scripts/parser/features/match.gd @@ -3,8 +3,6 @@ func test(): match i: "Hello": print("hello") - # This will fall through to the default case below. - continue "Good bye": print("bye") _: diff --git a/modules/gdscript/tests/scripts/parser/features/match.out b/modules/gdscript/tests/scripts/parser/features/match.out index 732885c7a2..a2cb94399c 100644 --- a/modules/gdscript/tests/scripts/parser/features/match.out +++ b/modules/gdscript/tests/scripts/parser/features/match.out @@ -1,4 +1,3 @@ GDTEST_OK hello -default This will match diff --git a/modules/gdscript/tests/scripts/parser/features/match_dictionary.gd b/modules/gdscript/tests/scripts/parser/features/match_dictionary.gd index 377dd25e9e..75857fb8ff 100644 --- a/modules/gdscript/tests/scripts/parser/features/match_dictionary.gd +++ b/modules/gdscript/tests/scripts/parser/features/match_dictionary.gd @@ -1,43 +1,43 @@ func foo(x): - match x: - {"key1": "value1", "key2": "value2"}: - print('{"key1": "value1", "key2": "value2"}') - {"key1": "value1", "key2"}: - print('{"key1": "value1", "key2"}') - {"key1", "key2": "value2"}: - print('{"key1", "key2": "value2"}') - {"key1", "key2"}: - print('{"key1", "key2"}') - {"key1": "value1"}: - print('{"key1": "value1"}') - {"key1"}: - print('{"key1"}') - _: - print("wildcard") + match x: + {"key1": "value1", "key2": "value2"}: + print('{"key1": "value1", "key2": "value2"}') + {"key1": "value1", "key2"}: + print('{"key1": "value1", "key2"}') + {"key1", "key2": "value2"}: + print('{"key1", "key2": "value2"}') + {"key1", "key2"}: + print('{"key1", "key2"}') + {"key1": "value1"}: + print('{"key1": "value1"}') + {"key1"}: + print('{"key1"}') + _: + print("wildcard") func bar(x): - match x: - {0}: - print("0") - {1}: - print("1") - {2}: - print("2") - _: - print("wildcard") + match x: + {0}: + print("0") + {1}: + print("1") + {2}: + print("2") + _: + print("wildcard") func test(): - foo({"key1": "value1", "key2": "value2"}) - foo({"key1": "value1", "key2": ""}) - foo({"key1": "", "key2": "value2"}) - foo({"key1": "", "key2": ""}) - foo({"key1": "value1"}) - foo({"key1": ""}) - foo({"key1": "value1", "key2": "value2", "key3": "value3"}) - foo({"key1": "value1", "key3": ""}) - foo({"key2": "value2"}) - foo({"key3": ""}) - bar({0: "0"}) - bar({1: "1"}) - bar({2: "2"}) - bar({3: "3"}) + foo({"key1": "value1", "key2": "value2"}) + foo({"key1": "value1", "key2": ""}) + foo({"key1": "", "key2": "value2"}) + foo({"key1": "", "key2": ""}) + foo({"key1": "value1"}) + foo({"key1": ""}) + foo({"key1": "value1", "key2": "value2", "key3": "value3"}) + foo({"key1": "value1", "key3": ""}) + foo({"key2": "value2"}) + foo({"key3": ""}) + bar({0: "0"}) + bar({1: "1"}) + bar({2: "2"}) + bar({3: "3"}) diff --git a/modules/gdscript/tests/scripts/parser/features/match_multiple_patterns_with_array.gd b/modules/gdscript/tests/scripts/parser/features/match_multiple_patterns_with_array.gd index dbe223f5f5..a278ea1154 100644 --- a/modules/gdscript/tests/scripts/parser/features/match_multiple_patterns_with_array.gd +++ b/modules/gdscript/tests/scripts/parser/features/match_multiple_patterns_with_array.gd @@ -1,26 +1,26 @@ func foo(x): - match x: - 1, [2]: - print('1, [2]') - _: - print('wildcard') + match x: + 1, [2]: + print('1, [2]') + _: + print('wildcard') func bar(x): - match x: - [1], [2], [3]: - print('[1], [2], [3]') - [4]: - print('[4]') - _: - print('wildcard') + match x: + [1], [2], [3]: + print('[1], [2], [3]') + [4]: + print('[4]') + _: + print('wildcard') func test(): - foo(1) - foo([2]) - foo(2) - bar([1]) - bar([2]) - bar([3]) - bar([4]) - bar([5]) + foo(1) + foo([2]) + foo(2) + bar([1]) + bar([2]) + bar([3]) + bar([4]) + bar([5]) diff --git a/modules/gdscript/tests/scripts/parser/features/match_multiple_variable_binds_in_pattern.gd b/modules/gdscript/tests/scripts/parser/features/match_multiple_variable_binds_in_pattern.gd index a0ae7fb17c..0a71f33c25 100644 --- a/modules/gdscript/tests/scripts/parser/features/match_multiple_variable_binds_in_pattern.gd +++ b/modules/gdscript/tests/scripts/parser/features/match_multiple_variable_binds_in_pattern.gd @@ -1,6 +1,6 @@ func test(): - match [1, 2, 3]: - [var a, var b, var c]: - print(a == 1) - print(b == 2) - print(c == 3) + match [1, 2, 3]: + [var a, var b, var c]: + print(a == 1) + print(b == 2) + print(c == 3) diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_assert.gd b/modules/gdscript/tests/scripts/parser/features/multiline_assert.gd new file mode 100644 index 0000000000..8c699c604d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_assert.gd @@ -0,0 +1,24 @@ +func test(): + var x := 5 + + assert(x > 0) + assert(x > 0,) + assert(x > 0, 'message') + assert(x > 0, 'message',) + + assert( + x > 0 + ) + assert( + x > 0, + ) + assert( + x > 0, + 'message' + ) + assert( + x > 0, + 'message', + ) + + print('OK') diff --git a/modules/gdscript/tests/scripts/parser/features/multiline_assert.out b/modules/gdscript/tests/scripts/parser/features/multiline_assert.out new file mode 100644 index 0000000000..1ccb591560 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/multiline_assert.out @@ -0,0 +1,2 @@ +GDTEST_OK +OK diff --git a/modules/gdscript/tests/scripts/parser/features/nested_match.gd b/modules/gdscript/tests/scripts/parser/features/nested_match.gd index aaddcc7e83..491d917a8e 100644 --- a/modules/gdscript/tests/scripts/parser/features/nested_match.gd +++ b/modules/gdscript/tests/scripts/parser/features/nested_match.gd @@ -8,11 +8,10 @@ func test(): 1234: print("2") match number: - 1234: - print("3") - continue + 4321: + print("Should not be printed") _: - print("Should also be printed") + print("3") match number: 1234: print("4") diff --git a/modules/gdscript/tests/scripts/parser/features/nested_match.out b/modules/gdscript/tests/scripts/parser/features/nested_match.out index 651d76cc59..c2d2e29a06 100644 --- a/modules/gdscript/tests/scripts/parser/features/nested_match.out +++ b/modules/gdscript/tests/scripts/parser/features/nested_match.out @@ -2,7 +2,6 @@ GDTEST_OK 1 2 3 -Should also be printed 4 5 6 diff --git a/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd new file mode 100644 index 0000000000..523959a016 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.gd @@ -0,0 +1,35 @@ +const π = PI +var ㄥ = π + +func test(): + var փորձարկում = "test" + prints("փորձարկում", փորձարկում) + var امتحان = "test" + prints("امتحان", امتحان) + var পরীক্ষা = "test" + prints("পরীক্ষা", পরীক্ষা) + var тест = "test" + prints("тест", тест) + var जाँच = "test" + prints("जाँच", जाँच) + var 기준 = "test" + prints("기준", 기준) + var 测试 = "test" + prints("测试", 测试) + var テスト = "test" + prints("テスト", テスト) + var 試験 = "test" + prints("試験", 試験) + var പരീക്ഷ = "test" + prints("പരീക്ഷ", പരീക്ഷ) + var ทดสอบ = "test" + prints("ทดสอบ", ทดสอบ) + var δοκιμή = "test" + prints("δοκιμή", δοκιμή) + + const d = 1.1 + _process(d) + print(is_equal_approx(ㄥ, PI + (d * PI))) + +func _process(Δ: float) -> void: + ㄥ += Δ * π diff --git a/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.out b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.out new file mode 100644 index 0000000000..c071380a8f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/unicode_identifiers.out @@ -0,0 +1,14 @@ +GDTEST_OK +փորձարկում test +امتحان test +পরীক্ষা test +тест test +जाँच test +기준 test +测试 test +テスト test +試験 test +പരീക്ഷ test +ทดสอบ test +δοκιμή test +true diff --git a/modules/gdscript/tests/scripts/parser/features/unnamed_enums_outer_conflicts.gd b/modules/gdscript/tests/scripts/parser/features/unnamed_enums_outer_conflicts.gd new file mode 100644 index 0000000000..4cbb464f59 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/unnamed_enums_outer_conflicts.gd @@ -0,0 +1,17 @@ +class A: + enum { X = 1 } + + class B: + enum { X = 2 } + +class C: + const X = 3 + + class D: + enum { X = 4 } + +func test(): + print(A.X) + print(A.B.X) + print(C.X) + print(C.D.X) diff --git a/modules/gdscript/tests/scripts/parser/features/unnamed_enums_outer_conflicts.out b/modules/gdscript/tests/scripts/parser/features/unnamed_enums_outer_conflicts.out new file mode 100644 index 0000000000..7536c38490 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/unnamed_enums_outer_conflicts.out @@ -0,0 +1,5 @@ +GDTEST_OK +1 +2 +3 +4 diff --git a/modules/gdscript/tests/scripts/parser/features/vector_inf.gd b/modules/gdscript/tests/scripts/parser/features/vector_inf.gd new file mode 100644 index 0000000000..039d51d9ed --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/vector_inf.gd @@ -0,0 +1,6 @@ +func test(): + var vec2: = Vector2.INF + var vec3: = Vector3.INF + + print(vec2.x == INF) + print(vec3.z == INF) diff --git a/modules/gdscript/tests/scripts/parser/features/vector_inf.out b/modules/gdscript/tests/scripts/parser/features/vector_inf.out new file mode 100644 index 0000000000..9d111a8322 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/vector_inf.out @@ -0,0 +1,3 @@ +GDTEST_OK +true +true diff --git a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd new file mode 100644 index 0000000000..e2caac8ffd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd @@ -0,0 +1,5 @@ +func test(): + var port = 0 # Only latin characters. + var pοrt = 1 # The "ο" is Greek omicron. + + prints(port, pοrt) diff --git a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out new file mode 100644 index 0000000000..c483396443 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out @@ -0,0 +1,6 @@ +GDTEST_OK +>> WARNING +>> Line: 3 +>> CONFUSABLE_IDENTIFIER +>> The identifier "pοrt" has misleading characters and might be confused with something else. +0 1 diff --git a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out index 13f759dd46..e89bb9226f 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out +++ b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out @@ -2,4 +2,4 @@ GDTEST_OK >> WARNING >> Line: 6 >> RETURN_VALUE_DISCARDED ->> The function 'i_return_int()' returns a value, but this value is never used. +>> The function 'i_return_int()' returns a value that will be discarded if not used. diff --git a/modules/gdscript/tests/scripts/parser/warnings/void_assignment.gd b/modules/gdscript/tests/scripts/parser/warnings/void_assignment.gd deleted file mode 100644 index b4a42b3e3d..0000000000 --- a/modules/gdscript/tests/scripts/parser/warnings/void_assignment.gd +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index 84c9598f9a..0000000000 --- a/modules/gdscript/tests/scripts/parser/warnings/void_assignment.out +++ /dev/null @@ -1,5 +0,0 @@ -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/bad_conversion_for_default_parameter.gd b/modules/gdscript/tests/scripts/runtime/errors/bad_conversion_for_default_parameter.gd new file mode 100644 index 0000000000..a72ac9b5ee --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/bad_conversion_for_default_parameter.gd @@ -0,0 +1,8 @@ +var weakling = 'not float' +func weak(x: float = weakling): + print(x) + print('typeof x is', typeof(x)) + +func test(): + print(typeof(weak())) + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/bad_conversion_for_default_parameter.out b/modules/gdscript/tests/scripts/runtime/errors/bad_conversion_for_default_parameter.out new file mode 100644 index 0000000000..8543cf976e --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/bad_conversion_for_default_parameter.out @@ -0,0 +1,8 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: weak() +>> runtime/errors/bad_conversion_for_default_parameter.gd +>> 2 +>> Trying to assign value of type 'String' to a variable of type 'float'. +0 +not ok diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.gd b/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.gd new file mode 100644 index 0000000000..a5ecaba38d --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.gd @@ -0,0 +1,6 @@ +const array: Array = [{}] + +func test(): + var dictionary := array[0] + var key: int = 0 + dictionary[key] = 0 diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out b/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out new file mode 100644 index 0000000000..2a97eaea44 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/constant_array_is_deep.gd +>> 6 +>> Invalid set index '0' (on base: 'Dictionary') with value of type 'int' diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.gd b/modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.gd new file mode 100644 index 0000000000..3e71cd0518 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.gd @@ -0,0 +1,4 @@ +const array: Array = [0] + +func test(): + array.push_back(0) diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.out b/modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.out new file mode 100644 index 0000000000..ba3e1c46c6 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/constant_array_push_back.out @@ -0,0 +1,7 @@ +GDTEST_RUNTIME_ERROR +>> ERROR +>> on function: push_back() +>> core/variant/array.cpp +>> 253 +>> Condition "_p->read_only" is true. +>> Array is in read-only state. diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.gd b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.gd new file mode 100644 index 0000000000..7b350e81ad --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.gd @@ -0,0 +1,4 @@ +const dictionary := {} + +func test(): + dictionary.erase(0) diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.out b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.out new file mode 100644 index 0000000000..3e7ca11a4f --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_erase.out @@ -0,0 +1,7 @@ +GDTEST_RUNTIME_ERROR +>> ERROR +>> on function: erase() +>> core/variant/dictionary.cpp +>> 177 +>> Condition "_p->read_only" is true. Returning: false +>> Dictionary is in read-only state. diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.gd b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.gd new file mode 100644 index 0000000000..4763210a7f --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.gd @@ -0,0 +1,6 @@ +const dictionary := {0: [0]} + +func test(): + var array := dictionary[0] + var key: int = 0 + array[key] = 0 diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out new file mode 100644 index 0000000000..c807db6b0c --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/constant_dictionary_is_deep.gd +>> 6 +>> Invalid set index '0' (on base: 'Array') with value of type 'int' diff --git a/modules/gdscript/tests/scripts/runtime/errors/use_return_value_of_free_call.gd b/modules/gdscript/tests/scripts/runtime/errors/use_return_value_of_free_call.gd new file mode 100644 index 0000000000..a3daf70627 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/use_return_value_of_free_call.gd @@ -0,0 +1,4 @@ +func test(): + var obj + obj = Node.new() + print(obj.free()) diff --git a/modules/gdscript/tests/scripts/runtime/errors/use_return_value_of_free_call.out b/modules/gdscript/tests/scripts/runtime/errors/use_return_value_of_free_call.out new file mode 100644 index 0000000000..5edaf19442 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/use_return_value_of_free_call.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/use_return_value_of_free_call.gd +>> 4 +>> Trying to get a return value of a method that returns "void" diff --git a/modules/gdscript/tests/scripts/runtime/errors/use_return_value_of_void_builtin_method_call.gd b/modules/gdscript/tests/scripts/runtime/errors/use_return_value_of_void_builtin_method_call.gd new file mode 100644 index 0000000000..49fb76ad1f --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/use_return_value_of_void_builtin_method_call.gd @@ -0,0 +1,4 @@ +func test(): + var value + value = [] + print(value.reverse()) diff --git a/modules/gdscript/tests/scripts/runtime/errors/use_return_value_of_void_builtin_method_call.out b/modules/gdscript/tests/scripts/runtime/errors/use_return_value_of_void_builtin_method_call.out new file mode 100644 index 0000000000..128356ff8a --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/use_return_value_of_void_builtin_method_call.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/use_return_value_of_void_builtin_method_call.gd +>> 4 +>> Trying to get a return value of a method that returns "void" diff --git a/modules/gdscript/tests/scripts/runtime/errors/use_return_value_of_void_native_method_call.gd b/modules/gdscript/tests/scripts/runtime/errors/use_return_value_of_void_native_method_call.gd new file mode 100644 index 0000000000..44f9aa467a --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/use_return_value_of_void_native_method_call.gd @@ -0,0 +1,4 @@ +func test(): + var obj + obj = RefCounted.new() + print(obj.notify_property_list_changed()) diff --git a/modules/gdscript/tests/scripts/runtime/errors/use_return_value_of_void_native_method_call.out b/modules/gdscript/tests/scripts/runtime/errors/use_return_value_of_void_native_method_call.out new file mode 100644 index 0000000000..e02c206778 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/use_return_value_of_void_native_method_call.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/use_return_value_of_void_native_method_call.gd +>> 4 +>> Trying to get a return value of a method that returns "void" diff --git a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd new file mode 100644 index 0000000000..bd38259cec --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd @@ -0,0 +1,35 @@ +# https://github.com/godotengine/godot/issues/63965 + +func test(): + var array_str: Array = [] + array_str.push_back("godot") + print("StringName in Array: ", &"godot" in array_str) + + var array_sname: Array = [] + array_sname.push_back(&"godot") + print("String in Array: ", "godot" in array_sname) + + # Not equal because the values are different types. + print("Arrays not equal: ", array_str != array_sname) + + var string_array: Array[String] = [] + var stringname_array: Array[StringName] = [] + + string_array.push_back(&"abc") + print("Array[String] insert converted: ", typeof(string_array[0]) == TYPE_STRING) + + stringname_array.push_back("abc") + print("Array[StringName] insert converted: ", typeof(stringname_array[0]) == TYPE_STRING_NAME) + + print("StringName in Array[String]: ", &"abc" in string_array) + print("String in Array[StringName]: ", "abc" in stringname_array) + + var packed_string_array: PackedStringArray = [] + assert(!packed_string_array.push_back("abc")) + print("StringName in PackedStringArray: ", &"abc" in packed_string_array) + + string_array.push_back("abc") + print("StringName finds String in Array: ", string_array.find(&"abc")) + + stringname_array.push_back(&"abc") + print("String finds StringName in Array: ", stringname_array.find("abc")) diff --git a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.out new file mode 100644 index 0000000000..98ab78e8f1 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.out @@ -0,0 +1,11 @@ +GDTEST_OK +StringName in Array: true +String in Array: true +Arrays not equal: true +Array[String] insert converted: true +Array[StringName] insert converted: true +StringName in Array[String]: true +String in Array[StringName]: true +StringName in PackedStringArray: true +StringName finds String in Array: 0 +String finds StringName in Array: 0 diff --git a/modules/gdscript/tests/scripts/runtime/features/await_on_void.gd b/modules/gdscript/tests/scripts/runtime/features/await_on_void.gd new file mode 100644 index 0000000000..1490a164c9 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/await_on_void.gd @@ -0,0 +1,7 @@ +func wait() -> void: + pass + +func test(): + @warning_ignore("redundant_await") + await wait() + print("end") diff --git a/modules/gdscript/tests/scripts/runtime/features/await_on_void.out b/modules/gdscript/tests/scripts/runtime/features/await_on_void.out new file mode 100644 index 0000000000..5bc3dcf2db --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/await_on_void.out @@ -0,0 +1,2 @@ +GDTEST_OK +end 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 index c6645c2c34..809d0d28a9 100644 --- a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd +++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd @@ -69,6 +69,10 @@ func test(): value = Transform3D() print(value == null) + # Projection + value = Projection() + print(value == null) + # Color value = Color() 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 index 639f6027b9..27423ab8e7 100644 --- a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out +++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out @@ -33,3 +33,4 @@ 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 index ee622bf22f..f46afb0f18 100644 --- 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 @@ -69,6 +69,10 @@ func test(): value = Transform3D() print(value != null) + # Projection + value = Projection() + print(value != null) + # Color value = Color() 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 index d1e332afba..a11c47854a 100644 --- 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 @@ -33,3 +33,4 @@ true true true true +true diff --git a/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.gd b/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.gd new file mode 100644 index 0000000000..9d0c6317b3 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.gd @@ -0,0 +1,19 @@ +func literal(x: float = 1): + print('x is ', x) + print('typeof x is ', typeof(x)) + +var inferring := 2 +func inferred(x: float = inferring): + print('x is ', x) + print('typeof x is ', typeof(x)) + +var weakling = 3 +func weak(x: float = weakling): + print('x is ', x) + print('typeof x is ', typeof(x)) + +func test(): + literal() + inferred() + weak() + print('ok') diff --git a/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out b/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out new file mode 100644 index 0000000000..a9ef4919cf --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/conversion_for_default_parameter.out @@ -0,0 +1,8 @@ +GDTEST_OK +x is 1 +typeof x is 3 +x is 2 +typeof x is 3 +x is 3 +typeof x is 3 +ok diff --git a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd new file mode 100644 index 0000000000..94bac1974f --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd @@ -0,0 +1,17 @@ +# https://github.com/godotengine/godot/issues/62957 + +func test(): + var string_dict = {} + string_dict["abc"] = 42 + var stringname_dict = {} + stringname_dict[&"abc"] = 24 + + print("String key is TYPE_STRING: ", typeof(string_dict.keys()[0]) == TYPE_STRING) + print("StringName key is TYPE_STRING: ", typeof(stringname_dict.keys()[0]) == TYPE_STRING) + + print("StringName gets String: ", string_dict.get(&"abc")) + print("String gets StringName: ", stringname_dict.get("abc")) + + stringname_dict[&"abc"] = 42 + # They compare equal because StringName keys are converted to String. + print("String Dictionary == StringName Dictionary: ", string_dict == stringname_dict) diff --git a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out new file mode 100644 index 0000000000..ab5b89d55c --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out @@ -0,0 +1,6 @@ +GDTEST_OK +String key is TYPE_STRING: true +StringName key is TYPE_STRING: true +StringName gets String: 42 +String gets StringName: 24 +String Dictionary == StringName Dictionary: true diff --git a/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd b/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd new file mode 100644 index 0000000000..48af734317 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd @@ -0,0 +1,17 @@ +# https://github.com/godotengine/godot/issues/71177 + +func test(): + builtin_method() + builtin_method_static() + print("done") + +func builtin_method(): + var pba := PackedByteArray() + @warning_ignore("return_value_discarded") + pba.resize(1) # Built-in validated. + + +func builtin_method_static(): + var _pba := PackedByteArray() + @warning_ignore("return_value_discarded") + Vector2.from_angle(PI) # Static built-in validated. diff --git a/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.out b/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.out new file mode 100644 index 0000000000..8e68c97774 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.out @@ -0,0 +1,2 @@ +GDTEST_OK +done diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_types.gd b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_types.gd new file mode 100644 index 0000000000..81355e0255 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_types.gd @@ -0,0 +1,51 @@ +const constant_float = 1.0 +const constant_int = 1 +enum { enum_value = 1 } + +class Iterator: + func _iter_init(_count): + return true + func _iter_next(_count): + return false + func _iter_get(_count) -> StringName: + return &'custom' + +func test(): + var hard_float := 1.0 + var hard_int := 1 + var hard_string := '0' + var hard_iterator := Iterator.new() + + var variant_float: Variant = hard_float + var variant_int: Variant = hard_int + var variant_string: Variant = hard_string + var variant_iterator: Variant = hard_iterator + + for i in 1.0: + print(typeof(i) == TYPE_FLOAT) + for i in 1: + print(typeof(i) == TYPE_INT) + for i in 'a': + print(typeof(i) == TYPE_STRING) + for i in Iterator.new(): + print(typeof(i) == TYPE_STRING_NAME) + + for i in hard_float: + print(typeof(i) == TYPE_FLOAT) + for i in hard_int: + print(typeof(i) == TYPE_INT) + for i in hard_string: + print(typeof(i) == TYPE_STRING) + for i in hard_iterator: + print(typeof(i) == TYPE_STRING_NAME) + + for i in variant_float: + print(typeof(i) == TYPE_FLOAT) + for i in variant_int: + print(typeof(i) == TYPE_INT) + for i in variant_string: + print(typeof(i) == TYPE_STRING) + for i in variant_iterator: + print(typeof(i) == TYPE_STRING_NAME) + + print('ok') diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_types.out b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_types.out new file mode 100644 index 0000000000..b3e82d52ef --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_types.out @@ -0,0 +1,14 @@ +GDTEST_OK +true +true +true +true +true +true +true +true +true +true +true +true +ok diff --git a/modules/gdscript/tests/scripts/runtime/features/gdscript.gd b/modules/gdscript/tests/scripts/runtime/features/gdscript.gd new file mode 100644 index 0000000000..e686cffc48 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/gdscript.gd @@ -0,0 +1,20 @@ +func test(): + var gdscr: = GDScript.new() + gdscr.source_code = ''' +extends Resource + +func test() -> void: + prints("Outer") + var inner = InnerClass.new() + +class InnerClass: + func _init() -> void: + prints("Inner") +''' + @warning_ignore("return_value_discarded") + gdscr.reload() + + var inst = gdscr.new() + + @warning_ignore("unsafe_method_access") + inst.test() diff --git a/modules/gdscript/tests/scripts/runtime/features/gdscript.out b/modules/gdscript/tests/scripts/runtime/features/gdscript.out new file mode 100644 index 0000000000..16114f57f7 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/gdscript.out @@ -0,0 +1,3 @@ +GDTEST_OK +Outer +Inner diff --git a/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd new file mode 100644 index 0000000000..55be021a90 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd @@ -0,0 +1,14 @@ +# https://github.com/godotengine/godot/issues/60145 + +func test(): + match "abc": + &"abc": + print("String matched StringName") + _: + print("no match") + + match &"abc": + "abc": + print("StringName matched String") + _: + print("no match") diff --git a/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out new file mode 100644 index 0000000000..9d5a18da3d --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out @@ -0,0 +1,3 @@ +GDTEST_OK +String matched StringName +StringName matched String diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd new file mode 100644 index 0000000000..252e100bda --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd @@ -0,0 +1,25 @@ +# https://github.com/godotengine/godot/pull/69620 + +var a: int = 1 + +func shadow_regular_assignment(a: Variant, b: Variant) -> void: + print(a) + print(self.a) + a = b + print(a) + print(self.a) + + +var v := Vector2(0.0, 0.0) + +func shadow_subscript_assignment(v: Vector2, x: float) -> void: + print(v) + print(self.v) + v.x += x + print(v) + print(self.v) + + +func test(): + shadow_regular_assignment('a', 'b') + shadow_subscript_assignment(Vector2(1.0, 1.0), 5.0) diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out new file mode 100644 index 0000000000..5b981bc8bb --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out @@ -0,0 +1,17 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> SHADOWED_VARIABLE +>> The local function parameter "a" is shadowing an already-declared variable at line 3. +>> WARNING +>> Line: 15 +>> SHADOWED_VARIABLE +>> The local function parameter "v" is shadowing an already-declared variable at line 13. +a +1 +b +1 +(1, 1) +(0, 0) +(6, 1) +(0, 0) diff --git a/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.gd b/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.gd new file mode 100644 index 0000000000..e24137a20d --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.gd @@ -0,0 +1,60 @@ +func test(): + # All combinations of 1/2/3 arguments, each being int/float. + + for number in range(5): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(5.2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + + for number in range(1, 5): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1, 5.2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1.2, 5): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1.2, 5.2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + + for number in range(1, 5, 2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1, 5, 2.2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1, 5.2, 2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1, 5.2, 2.2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1.2, 5, 2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1.2, 5.2, 2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1.2, 5, 2.2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + for number in range(1.2, 5.2, 2.2): + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") diff --git a/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.out b/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.gd b/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.gd new file mode 100644 index 0000000000..63c3b84305 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.gd @@ -0,0 +1,77 @@ +func test(): + # All combinations of 1/2/3 arguments, each being int/float. + # Store result in variable to ensure actual array is created (avoid `for` + `range` optimization). + + var result + + result = range(5) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(5.2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + + result = range(1, 5) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1, 5.2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1.2, 5) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1.2, 5.2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + + result = range(1, 5, 2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1, 5, 2.2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1, 5.2, 2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1, 5.2, 2.2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1.2, 5, 2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1.2, 5.2, 2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1.2, 5, 2.2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") + + result = range(1.2, 5.2, 2.2) + for number in result: + if typeof(number) != TYPE_INT: + print("Number returned from `range` was not an int!") diff --git a/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.out b/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd new file mode 100644 index 0000000000..2f55059334 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd @@ -0,0 +1,45 @@ +# https://github.com/godotengine/godot/issues/70964 + +func test(): + test_construct(0, false) + test_utility(0, false) + test_builtin_call(Vector2.UP, false) + test_builtin_call_validated(Vector2.UP, false) + test_object_call(RefCounted.new(), false) + test_object_call_method_bind(Resource.new(), false) + test_object_call_ptrcall(RefCounted.new(), false) + + print("end") + +func test_construct(v, f): + Vector2(v, v) # Built-in type construct. + assert(not f) # Test unary operator reading from `nil`. + +func test_utility(v, f): + abs(v) # Utility function. + assert(not f) # Test unary operator reading from `nil`. + +func test_builtin_call(v, f): + @warning_ignore("unsafe_method_access") + v.angle() # Built-in method call. + assert(not f) # Test unary operator reading from `nil`. + +func test_builtin_call_validated(v: Vector2, f): + @warning_ignore("return_value_discarded") + v.abs() # Built-in method call validated. + assert(not f) # Test unary operator reading from `nil`. + +func test_object_call(v, f): + @warning_ignore("unsafe_method_access") + v.get_reference_count() # Native type method call. + assert(not f) # Test unary operator reading from `nil`. + +func test_object_call_method_bind(v: Resource, f): + @warning_ignore("return_value_discarded") + v.duplicate() # Native type method call with MethodBind. + assert(not f) # Test unary operator reading from `nil`. + +func test_object_call_ptrcall(v: RefCounted, f): + @warning_ignore("return_value_discarded") + v.get_reference_count() # Native type method call with ptrcall. + assert(not f) # Test unary operator reading from `nil`. diff --git a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.out b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.out new file mode 100644 index 0000000000..5bc3dcf2db --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.out @@ -0,0 +1,2 @@ +GDTEST_OK +end diff --git a/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd new file mode 100644 index 0000000000..f8bd46523e --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd @@ -0,0 +1,11 @@ +# https://github.com/godotengine/godot/issues/64171 + +func test(): + print("Compare ==: ", "abc" == &"abc") + print("Compare ==: ", &"abc" == "abc") + print("Compare !=: ", "abc" != &"abc") + print("Compare !=: ", &"abc" != "abc") + + print("Concat: ", "abc" + &"def") + print("Concat: ", &"abc" + "def") + print("Concat: ", &"abc" + &"def") diff --git a/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out new file mode 100644 index 0000000000..7e9c364b60 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out @@ -0,0 +1,8 @@ +GDTEST_OK +Compare ==: true +Compare ==: true +Compare !=: false +Compare !=: false +Concat: abcdef +Concat: abcdef +Concat: abcdef diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_assignment.gd b/modules/gdscript/tests/scripts/runtime/features/typed_assignment.gd new file mode 100644 index 0000000000..22e54cf91c --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_assignment.gd @@ -0,0 +1,9 @@ +func test(): + var x: int = 2 + var y = 3.14 + var z := 2.72 + print(typeof(x)) + x = y + print(typeof(x)) + x = z + print(typeof(x)) diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_assignment.out b/modules/gdscript/tests/scripts/runtime/features/typed_assignment.out new file mode 100644 index 0000000000..4a268dd8e0 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_assignment.out @@ -0,0 +1,12 @@ +GDTEST_OK +>> WARNING +>> Line: 6 +>> NARROWING_CONVERSION +>> Narrowing conversion (float is converted to int and loses precision). +>> WARNING +>> Line: 8 +>> NARROWING_CONVERSION +>> Narrowing conversion (float is converted to int and loses precision). +2 +2 +2 diff --git a/modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.gd b/modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.gd new file mode 100644 index 0000000000..efa8270526 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.gd @@ -0,0 +1,9 @@ +# https://github.com/godotengine/godot/issues/71172 + +func test(): + @warning_ignore("narrowing_conversion") + var foo: int = 0.0 + print(typeof(foo) == TYPE_INT) + var dict : Dictionary = {"a":0.0} + foo = dict.get("a") + print(typeof(foo) == TYPE_INT) diff --git a/modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.out b/modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.out new file mode 100644 index 0000000000..9d111a8322 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.out @@ -0,0 +1,3 @@ +GDTEST_OK +true +true diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp index cbcd7b2955..ad38312abe 100644 --- a/modules/gdscript/tests/test_gdscript.cpp +++ b/modules/gdscript/tests/test_gdscript.cpp @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* test_gdscript.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* test_gdscript.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #include "test_gdscript.h" diff --git a/modules/gdscript/tests/test_gdscript.h b/modules/gdscript/tests/test_gdscript.h index b6b1f26203..d719e3d94a 100644 --- a/modules/gdscript/tests/test_gdscript.h +++ b/modules/gdscript/tests/test_gdscript.h @@ -1,32 +1,32 @@ -/*************************************************************************/ -/* test_gdscript.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ +/**************************************************************************/ +/* test_gdscript.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ #ifndef TEST_GDSCRIPT_H #define TEST_GDSCRIPT_H |