diff options
Diffstat (limited to 'modules/gdscript')
93 files changed, 1912 insertions, 838 deletions
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 85ff5080f0..5a28246555 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="@GDScript" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Built-in GDScript functions. + Built-in GDScript constants, functions, and annotations. </brief_description> <description> A list of GDScript-specific utility functions and annotations accessible from any script. @@ -139,7 +139,7 @@ print(is_instance_of(a, MyClass)) print(is_instance_of(a, MyClass.InnerClass)) [/codeblock] - [b]Note:[/b] If [param value] and/or [param type] are freed objects (see [method @GlobalScope.is_instance_valid]), or [param type] is not one of the above options, this method will raise an runtime error. + [b]Note:[/b] If [param value] and/or [param type] are freed objects (see [method @GlobalScope.is_instance_valid]), or [param type] is not one of the above options, this method will raise a runtime error. See also [method @GlobalScope.typeof], [method type_exists], [method Array.is_same_typed] (and other [Array] methods). </description> </method> @@ -170,6 +170,7 @@ [b]Important:[/b] The path must be absolute. A relative path will always return [code]null[/code]. This function is a simplified version of [method ResourceLoader.load], which can be used for more advanced scenarios. [b]Note:[/b] Files have to be imported into the engine first to load them using this function. If you want to load [Image]s at run-time, you may use [method Image.load]. If you want to import audio files, you can use the snippet described in [member AudioStreamMP3.data]. + [b]Note:[/b] If [member ProjectSettings.editor/export/convert_text_resources_to_binary] is [code]true[/code], [method @GDScript.load] will not be able to read converted files in an exported project. If you rely on run-time loading of files present within the PCK, set [member ProjectSettings.editor/export/convert_text_resources_to_binary] to [code]false[/code]. </description> </method> <method name="preload"> @@ -227,8 +228,8 @@ To iterate over an [Array] backwards, use: [codeblock] var array = [3, 6, 9] - for i in range(array.size(), 0, -1): - print(array[i - 1]) + for i in range(array.size() - 1, -1, -1): + print(array[i]) [/codeblock] Output: [codeblock] @@ -273,7 +274,7 @@ [b]Warning:[/b] Numeric infinity is only a concept with floating-point numbers, and has no equivalent for integers. Dividing an integer number by [code]0[/code] will not result in [constant INF] and will result in a run-time error instead. </constant> <constant name="NAN" value="nan"> - "Not a Number", an invalid floating-point value. [constant NAN] has special properties, including that it is not equal to itself ([code]NAN == NAN[/code] returns [code]false[/code]). It is output by some invalid operations, such as dividing floating-point [code]0.0[/code] by [code]0.0[/code]. + "Not a Number", an invalid floating-point value. [constant NAN] has special properties, including that [code]!=[/code] always returns [code]true[/code], while other comparison operators always return [code]false[/code]. This is true even when comparing with itself ([code]NAN == NAN[/code] returns [code]false[/code] and [code]NAN != NAN[/code] returns [code]true[/code]). It is returned by some invalid operations, such as dividing floating-point [code]0.0[/code] by [code]0.0[/code]. [b]Warning:[/b] "Not a Number" is only a concept with floating-point numbers, and has no equivalent for integers. Dividing an integer [code]0[/code] by [code]0[/code] will not result in [constant NAN] and will result in a run-time error instead. </constant> </constants> @@ -457,6 +458,16 @@ [/codeblock] </description> </annotation> + <annotation name="@export_flags_avoidance"> + <return type="void" /> + <description> + Export an integer property as a bit flag field for navigation avoidance layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/avoidance/layer_1]. + See also [constant PROPERTY_HINT_LAYERS_AVOIDANCE]. + [codeblock] + @export_flags_avoidance var avoidance_layers: int + [/codeblock] + </description> + </annotation> <annotation name="@export_global_dir"> <return type="void" /> <description> @@ -486,7 +497,7 @@ <param index="1" name="prefix" type="String" default="""" /> <description> Define a new group for the following exported properties. This helps to organize properties in the Inspector dock. Groups can be added with an optional [param prefix], which would make group to only consider properties that have this prefix. The grouping will break on the first property that doesn't have a prefix. The prefix is also removed from the property's name in the Inspector dock. - If no [param prefix] is provided, the every following property is added to the group. The group ends when then next group or category is defined. You can also force end a group by using this annotation with empty strings for parameters, [code]@export_group("", "")[/code]. + If no [param prefix] is provided, then every following property will be added to the group. The group ends when then next group or category is defined. You can also force end a group by using this annotation with empty strings for parameters, [code]@export_group("", "")[/code]. Groups cannot be nested, use [annotation @export_subgroup] to add subgroups within groups. See also [constant PROPERTY_USAGE_GROUP]. [codeblock] @@ -609,7 +620,7 @@ <param index="3" name="transfer_channel" type="int" default="0" /> <description> Mark the following method for remote procedure calls. See [url=$DOCS_URL/tutorials/networking/high_level_multiplayer.html]High-level multiplayer[/url]. - The order of [code]mode[/code], [code]sync[/code] and [code]transfer_mode[/code] does not matter and all arguments can be omitted, but [code]transfer_channel[/code] always has to be the last argument. The accepted values for [code]mode[/code] are [code]"any_peer"[/code] or [code]"authority"[/code], for [code]sync[/code] are [code]"call_remote"[/code] or [code]"call_local"[/code] and for [code]transfer_mode[/code] are [code]"unreliable"[/code], [code]"unreliable_ordered"[/code] or [code]"reliable"[/code]. + The order of [param mode], [param sync] and [param transfer_mode] does not matter and all arguments can be omitted, but [param transfer_channel] always has to be the last argument. The accepted values for [param mode] are [code]"any_peer"[/code] or [code]"authority"[/code], for [param sync] are [code]"call_remote"[/code] or [code]"call_local"[/code] and for [param transfer_mode] are [code]"unreliable"[/code], [code]"unreliable_ordered"[/code] or [code]"reliable"[/code]. [codeblock] @rpc func fn(): pass @@ -622,6 +633,12 @@ [/codeblock] </description> </annotation> + <annotation name="@static_unload"> + <return type="void" /> + <description> + Make a script with static variables to not persist after all references are lost. If the script is loaded again the static variables will revert to their default values. + </description> + </annotation> <annotation name="@tool"> <return type="void" /> <description> diff --git a/modules/gdscript/doc_classes/GDScript.xml b/modules/gdscript/doc_classes/GDScript.xml index 556f747eb3..f383eed480 100644 --- a/modules/gdscript/doc_classes/GDScript.xml +++ b/modules/gdscript/doc_classes/GDScript.xml @@ -5,7 +5,8 @@ </brief_description> <description> A script implemented in the GDScript programming language. The script extends the functionality of all objects that instantiate it. - [method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes. + Calling [method new] creates a new instance of the script. [method Object.set_script] extends an existing object, if that object's class matches one of the script's base classes. + If you are looking for GDScript's built-in functions, see [@GDScript] instead. </description> <tutorials> <link title="GDScript documentation index">$DOCS_URL/tutorials/scripting/gdscript/index.html</link> diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp new file mode 100644 index 0000000000..ce64d79747 --- /dev/null +++ b/modules/gdscript/editor/gdscript_docgen.cpp @@ -0,0 +1,272 @@ +/**************************************************************************/ +/* gdscript_docgen.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_docgen.h" +#include "../gdscript.h" + +using GDP = GDScriptParser; +using GDType = GDP::DataType; + +static String _get_script_path(const String &p_path) { + return vformat(R"("%s")", p_path.get_slice("://", 1)); +} + +static String _get_class_name(const GDP::ClassNode &p_class) { + const GDP::ClassNode *curr_class = &p_class; + if (!curr_class->identifier) { // All inner classes have a identifier, so this is the outer class + return _get_script_path(curr_class->fqcn); + } + + String full_name = curr_class->identifier->name; + while (curr_class->outer) { + curr_class = curr_class->outer; + if (!curr_class->identifier) { // All inner classes have a identifier, so this is the outer class + return vformat("%s.%s", _get_script_path(curr_class->fqcn), full_name); + } + full_name = vformat("%s.%s", curr_class->identifier->name, full_name); + } + return full_name; +} + +static PropertyInfo _property_info_from_datatype(const GDType &p_type) { + PropertyInfo pi; + pi.type = p_type.builtin_type; + if (p_type.kind == GDType::CLASS) { + pi.class_name = _get_class_name(*p_type.class_type); + } else if (p_type.kind == GDType::ENUM && p_type.enum_type != StringName()) { + pi.type = Variant::INT; // Only int types are recognized as enums by the EditorHelp + pi.usage |= PROPERTY_USAGE_CLASS_IS_ENUM; + // Replace :: from enum's use of fully qualified class names with regular . + pi.class_name = String(p_type.native_type).replace("::", "."); + } else if (p_type.kind == GDType::NATIVE) { + pi.class_name = p_type.native_type; + } + return pi; +} + +void GDScriptDocGen::generate_docs(GDScript *p_script, const GDP::ClassNode *p_class) { + p_script->_clear_doc(); + + DocData::ClassDoc &doc = p_script->doc; + + doc.script_path = _get_script_path(p_script->get_script_path()); + if (p_script->name.is_empty()) { + doc.name = doc.script_path; + } else { + doc.name = p_script->name; + } + + if (p_script->_owner) { + doc.name = p_script->_owner->doc.name + "." + doc.name; + doc.script_path = doc.script_path + "." + doc.name; + } + + doc.is_script_doc = true; + + if (p_script->base.is_valid() && p_script->base->is_valid()) { + if (!p_script->base->doc.name.is_empty()) { + doc.inherits = p_script->base->doc.name; + } else { + doc.inherits = p_script->base->get_instance_base_type(); + } + } else if (p_script->native.is_valid()) { + doc.inherits = p_script->native->get_name(); + } + + doc.brief_description = p_class->doc_brief_description; + doc.description = p_class->doc_description; + for (const Pair<String, String> &p : p_class->doc_tutorials) { + DocData::TutorialDoc td; + td.title = p.first; + td.link = p.second; + doc.tutorials.append(td); + } + + for (const GDP::ClassNode::Member &member : p_class->members) { + switch (member.type) { + case GDP::ClassNode::Member::CLASS: { + const GDP::ClassNode *inner_class = member.m_class; + const StringName &class_name = inner_class->identifier->name; + + p_script->member_lines[class_name] = inner_class->start_line; + + // Recursively generate inner class docs + // Needs inner GDScripts to exist: previously generated in GDScriptCompiler::make_scripts() + GDScriptDocGen::generate_docs(*p_script->subclasses[class_name], inner_class); + } break; + + case GDP::ClassNode::Member::CONSTANT: { + const GDP::ConstantNode *m_const = member.constant; + const StringName &const_name = member.constant->identifier->name; + + p_script->member_lines[const_name] = m_const->start_line; + + DocData::ConstantDoc const_doc; + DocData::constant_doc_from_variant(const_doc, const_name, m_const->initializer->reduced_value, m_const->doc_description); + doc.constants.push_back(const_doc); + } break; + + case GDP::ClassNode::Member::FUNCTION: { + const GDP::FunctionNode *m_func = member.function; + const StringName &func_name = m_func->identifier->name; + + p_script->member_lines[func_name] = m_func->start_line; + + MethodInfo mi; + mi.name = func_name; + + if (m_func->return_type) { + mi.return_val = _property_info_from_datatype(m_func->return_type->get_datatype()); + } + for (const GDScriptParser::ParameterNode *p : m_func->parameters) { + PropertyInfo pi = _property_info_from_datatype(p->get_datatype()); + pi.name = p->identifier->name; + mi.arguments.push_back(pi); + } + + DocData::MethodDoc method_doc; + DocData::method_doc_from_methodinfo(method_doc, mi, m_func->doc_description); + doc.methods.push_back(method_doc); + } break; + + case GDP::ClassNode::Member::SIGNAL: { + const GDP::SignalNode *m_signal = member.signal; + const StringName &signal_name = m_signal->identifier->name; + + p_script->member_lines[signal_name] = m_signal->start_line; + + MethodInfo mi; + mi.name = signal_name; + for (const GDScriptParser::ParameterNode *p : m_signal->parameters) { + PropertyInfo pi = _property_info_from_datatype(p->get_datatype()); + pi.name = p->identifier->name; + mi.arguments.push_back(pi); + } + + DocData::MethodDoc signal_doc; + DocData::signal_doc_from_methodinfo(signal_doc, mi, m_signal->doc_description); + doc.signals.push_back(signal_doc); + } break; + + case GDP::ClassNode::Member::VARIABLE: { + const GDP::VariableNode *m_var = member.variable; + const StringName &var_name = m_var->identifier->name; + + p_script->member_lines[var_name] = m_var->start_line; + + DocData::PropertyDoc prop_doc; + + prop_doc.name = var_name; + prop_doc.description = m_var->doc_description; + + GDType dt = m_var->get_datatype(); + switch (dt.kind) { + case GDType::CLASS: + prop_doc.type = _get_class_name(*dt.class_type); + break; + case GDType::VARIANT: + prop_doc.type = "Variant"; + break; + case GDType::ENUM: + prop_doc.type = Variant::get_type_name(dt.builtin_type); + // Replace :: from enum's use of fully qualified class names with regular . + prop_doc.enumeration = String(dt.native_type).replace("::", "."); + break; + case GDType::NATIVE:; + prop_doc.type = dt.native_type; + break; + case GDType::BUILTIN: + prop_doc.type = Variant::get_type_name(dt.builtin_type); + break; + default: + // SCRIPT: can be preload()'d and perhaps used as types directly? + // RESOLVING & UNRESOLVED should never happen since docgen requires analyzing w/o errors + break; + } + + if (m_var->property == GDP::VariableNode::PROP_SETGET) { + if (m_var->setter_pointer != nullptr) { + prop_doc.setter = m_var->setter_pointer->name; + } + if (m_var->getter_pointer != nullptr) { + prop_doc.getter = m_var->getter_pointer->name; + } + } + + if (m_var->initializer && m_var->initializer->is_constant) { + prop_doc.default_value = m_var->initializer->reduced_value.get_construct_string().replace("\n", ""); + } + + prop_doc.overridden = false; + + doc.properties.push_back(prop_doc); + } break; + + case GDP::ClassNode::Member::ENUM: { + const GDP::EnumNode *m_enum = member.m_enum; + StringName name = m_enum->identifier->name; + + p_script->member_lines[name] = m_enum->start_line; + + for (const GDP::EnumNode::Value &val : m_enum->values) { + DocData::ConstantDoc const_doc; + const_doc.name = val.identifier->name; + const_doc.value = String(Variant(val.value)); + const_doc.is_value_valid = true; + const_doc.description = val.doc_description; + const_doc.enumeration = name; + + doc.enums[const_doc.name] = const_doc.description; + doc.constants.push_back(const_doc); + } + + } break; + + case GDP::ClassNode::Member::ENUM_VALUE: { + const GDP::EnumNode::Value &m_enum_val = member.enum_value; + const StringName &name = m_enum_val.identifier->name; + + p_script->member_lines[name] = m_enum_val.identifier->start_line; + + DocData::ConstantDoc constant_doc; + constant_doc.enumeration = "@unnamed_enums"; + DocData::constant_doc_from_variant(constant_doc, name, m_enum_val.value, m_enum_val.doc_description); + doc.constants.push_back(constant_doc); + } break; + case GDP::ClassNode::Member::GROUP: + case GDP::ClassNode::Member::UNDEFINED: + default: + break; + } + } + + // Add doc to the outer-most class. + p_script->_add_doc(doc); +} diff --git a/modules/gdscript/editor/gdscript_docgen.h b/modules/gdscript/editor/gdscript_docgen.h new file mode 100644 index 0000000000..bb3647196a --- /dev/null +++ b/modules/gdscript/editor/gdscript_docgen.h @@ -0,0 +1,42 @@ +/**************************************************************************/ +/* gdscript_docgen.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_DOCGEN_H +#define GDSCRIPT_DOCGEN_H + +#include "../gdscript_parser.h" +#include "core/doc_data.h" + +class GDScriptDocGen { +public: + static void generate_docs(GDScript *p_script, const GDScriptParser::ClassNode *p_class); +}; + +#endif // GDSCRIPT_DOCGEN_H diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp index 00d50d1737..3458eb43b5 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp @@ -311,6 +311,14 @@ void GDScriptEditorTranslationParserPlugin::_extract_from_call(GDScriptParser::C } } } + + if (p_call->callee && p_call->callee->type == GDScriptParser::Node::SUBSCRIPT) { + GDScriptParser::SubscriptNode *subscript_node = static_cast<GDScriptParser::SubscriptNode *>(p_call->callee); + if (subscript_node->base && subscript_node->base->type == GDScriptParser::Node::CALL) { + GDScriptParser::CallNode *call_node = static_cast<GDScriptParser::CallNode *>(subscript_node->base); + _extract_from_call(call_node); + } + } } void GDScriptEditorTranslationParserPlugin::_extract_fd_literals(GDScriptParser::ExpressionNode *p_expression) { diff --git a/modules/gdscript/editor/script_templates/RichTextEffect/default.gd b/modules/gdscript/editor/script_templates/RichTextEffect/default.gd new file mode 100644 index 0000000000..c79eeb91ec --- /dev/null +++ b/modules/gdscript/editor/script_templates/RichTextEffect/default.gd @@ -0,0 +1,17 @@ +# meta-description: Base template for rich text effects + +@tool +class_name _CLASS_ +extends _BASE_ + + +# To use this effect: +# - Enable BBCode on a RichTextLabel. +# - Register this effect on the label. +# - Use [_CLASS_ param=2.0]hello[/_CLASS_] in text. +var bbcode := "_CLASS_" + + +func _process_custom_fx(char_fx: CharFXTransform) -> bool: + var param: float = char_fx.env.get("param", 1.0) + return true diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 2646c1ad15..d0790aba25 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -52,6 +52,7 @@ #ifdef TOOLS_ENABLED #include "editor/editor_paths.h" +#include "editor/gdscript_docgen.h" #endif /////////////////////////// @@ -340,12 +341,11 @@ void GDScript::_get_script_property_list(List<PropertyInfo> *r_list, bool p_incl r_list->push_back(E); } - props.clear(); - if (!p_include_base) { break; } + props.clear(); sptr = sptr->_base; } } @@ -461,9 +461,9 @@ void GDScript::_update_exports_values(HashMap<StringName, Variant> &values, List } void GDScript::_add_doc(const DocData::ClassDoc &p_inner_class) { - if (_owner) { + if (_owner) { // Only the top-level class stores doc info _owner->_add_doc(p_inner_class); - } else { + } else { // Remove old docs, add new for (int i = 0; i < docs.size(); i++) { if (docs[i].name == p_inner_class.name) { docs.remove_at(i); @@ -478,167 +478,6 @@ void GDScript::_clear_doc() { docs.clear(); doc = DocData::ClassDoc(); } - -void GDScript::_update_doc() { - _clear_doc(); - - doc.script_path = vformat(R"("%s")", get_script_path().get_slice("://", 1)); - if (!name.is_empty()) { - doc.name = name; - } else { - doc.name = doc.script_path; - } - - if (_owner) { - doc.name = _owner->doc.name + "." + doc.name; - doc.script_path = doc.script_path + "." + doc.name; - } - - doc.is_script_doc = true; - - if (base.is_valid() && base->is_valid()) { - if (!base->doc.name.is_empty()) { - doc.inherits = base->doc.name; - } else { - doc.inherits = base->get_instance_base_type(); - } - } else if (native.is_valid()) { - doc.inherits = native->get_name(); - } - - doc.brief_description = doc_brief_description; - doc.description = doc_description; - doc.tutorials = doc_tutorials; - - for (const KeyValue<String, DocData::EnumDoc> &E : doc_enums) { - if (!E.value.description.is_empty()) { - doc.enums[E.key] = E.value.description; - } - } - - List<MethodInfo> methods; - _get_script_method_list(&methods, false); - for (int i = 0; i < methods.size(); i++) { - // Ignore internal methods. - if (methods[i].name[0] == '@') { - continue; - } - - DocData::MethodDoc method_doc; - const String &class_name = methods[i].name; - if (member_functions.has(class_name)) { - GDScriptFunction *fn = member_functions[class_name]; - - // Change class name if return type is script reference. - GDScriptDataType return_type = fn->get_return_type(); - if (return_type.kind == GDScriptDataType::GDSCRIPT) { - methods[i].return_val.class_name = _get_gdscript_reference_class_name(Object::cast_to<GDScript>(return_type.script_type)); - } - - // Change class name if argument is script reference. - for (int j = 0; j < fn->get_argument_count(); j++) { - GDScriptDataType arg_type = fn->get_argument_type(j); - if (arg_type.kind == GDScriptDataType::GDSCRIPT) { - methods[i].arguments[j].class_name = _get_gdscript_reference_class_name(Object::cast_to<GDScript>(arg_type.script_type)); - } - } - } - if (doc_functions.has(methods[i].name)) { - DocData::method_doc_from_methodinfo(method_doc, methods[i], doc_functions[methods[i].name]); - } else { - DocData::method_doc_from_methodinfo(method_doc, methods[i], String()); - } - doc.methods.push_back(method_doc); - } - - List<PropertyInfo> props; - _get_script_property_list(&props, false); - for (int i = 0; i < props.size(); i++) { - if (props[i].usage & PROPERTY_USAGE_CATEGORY || props[i].usage & PROPERTY_USAGE_GROUP || props[i].usage & PROPERTY_USAGE_SUBGROUP) { - continue; - } - ScriptMemberInfo scr_member_info; - scr_member_info.propinfo = props[i]; - scr_member_info.propinfo.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - if (member_indices.has(props[i].name)) { - const MemberInfo &mi = member_indices[props[i].name]; - scr_member_info.setter = mi.setter; - scr_member_info.getter = mi.getter; - if (mi.data_type.kind == GDScriptDataType::GDSCRIPT) { - scr_member_info.propinfo.class_name = _get_gdscript_reference_class_name( - Object::cast_to<GDScript>(mi.data_type.script_type)); - } - } - if (member_default_values.has(props[i].name)) { - scr_member_info.has_default_value = true; - scr_member_info.default_value = member_default_values[props[i].name]; - } - if (doc_variables.has(props[i].name)) { - scr_member_info.doc_string = doc_variables[props[i].name]; - } - - DocData::PropertyDoc prop_doc; - DocData::property_doc_from_scriptmemberinfo(prop_doc, scr_member_info); - doc.properties.push_back(prop_doc); - } - - List<MethodInfo> signals; - _get_script_signal_list(&signals, false); - for (int i = 0; i < signals.size(); i++) { - DocData::MethodDoc signal_doc; - if (doc_signals.has(signals[i].name)) { - DocData::signal_doc_from_methodinfo(signal_doc, signals[i], doc_signals[signals[i].name]); - } else { - DocData::signal_doc_from_methodinfo(signal_doc, signals[i], String()); - } - doc.signals.push_back(signal_doc); - } - - for (const KeyValue<StringName, Variant> &E : constants) { - if (subclasses.has(E.key)) { - continue; - } - - // Enums. - bool is_enum = false; - if (E.value.get_type() == Variant::DICTIONARY) { - if (doc_enums.has(E.key)) { - is_enum = true; - for (int i = 0; i < doc_enums[E.key].values.size(); i++) { - doc_enums[E.key].values.write[i].enumeration = E.key; - doc.constants.push_back(doc_enums[E.key].values[i]); - } - } - } - if (!is_enum && doc_enums.has("@unnamed_enums")) { - for (int i = 0; i < doc_enums["@unnamed_enums"].values.size(); i++) { - if (E.key == doc_enums["@unnamed_enums"].values[i].name) { - is_enum = true; - DocData::ConstantDoc constant_doc; - constant_doc.enumeration = "@unnamed_enums"; - DocData::constant_doc_from_variant(constant_doc, E.key, E.value, doc_enums["@unnamed_enums"].values[i].description); - doc.constants.push_back(constant_doc); - break; - } - } - } - if (!is_enum) { - DocData::ConstantDoc constant_doc; - String const_description; - if (doc_constants.has(E.key)) { - const_description = doc_constants[E.key]; - } - DocData::constant_doc_from_variant(constant_doc, E.key, E.value, const_description); - doc.constants.push_back(constant_doc); - } - } - - for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { - E.value->_update_doc(); - } - - _add_doc(doc); -} #endif bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderScriptInstance *p_instance_to_update) { @@ -812,6 +651,49 @@ String GDScript::_get_debug_path() const { } } +Error GDScript::_static_init() { + if (static_initializer) { + Callable::CallError call_err; + static_initializer->call(nullptr, nullptr, 0, call_err); + if (call_err.error != Callable::CallError::CALL_OK) { + return ERR_CANT_CREATE; + } + } + Error err = OK; + for (KeyValue<StringName, Ref<GDScript>> &inner : subclasses) { + err = inner.value->_static_init(); + if (err) { + break; + } + } + return err; +} + +#ifdef TOOLS_ENABLED + +void GDScript::_save_old_static_data() { + old_static_variables_indices = static_variables_indices; + old_static_variables = static_variables; + for (KeyValue<StringName, Ref<GDScript>> &inner : subclasses) { + inner.value->_save_old_static_data(); + } +} + +void GDScript::_restore_old_static_data() { + for (KeyValue<StringName, MemberInfo> &E : old_static_variables_indices) { + if (static_variables_indices.has(E.key)) { + static_variables.write[static_variables_indices[E.key].index] = old_static_variables[E.value.index]; + } + } + old_static_variables_indices.clear(); + old_static_variables.clear(); + for (KeyValue<StringName, Ref<GDScript>> &inner : subclasses) { + inner.value->_restore_old_static_data(); + } +} + +#endif + Error GDScript::reload(bool p_keep_state) { if (reloading) { return OK; @@ -857,6 +739,14 @@ Error GDScript::reload(bool p_keep_state) { } } + bool can_run = ScriptServer::is_scripting_enabled() || is_tool(); + +#ifdef TOOLS_ENABLED + if (p_keep_state && can_run && is_valid()) { + _save_old_static_data(); + } +#endif + valid = false; GDScriptParser parser; Error err = parser.parse(source, path, false); @@ -887,7 +777,7 @@ Error GDScript::reload(bool p_keep_state) { return ERR_PARSE_ERROR; } - bool can_run = ScriptServer::is_scripting_enabled() || parser.is_tool(); + can_run = ScriptServer::is_scripting_enabled() || parser.is_tool(); GDScriptCompiler compiler; err = compiler.compile(&parser, this, p_keep_state); @@ -905,6 +795,13 @@ Error GDScript::reload(bool p_keep_state) { return err; } } + +#ifdef TOOLS_ENABLED + // Done after compilation because it needs the GDScript object's inner class GDScript objects, + // which are made by calling make_scripts() within compiler.compile() above. + GDScriptDocGen::generate_docs(this, parser.get_tree()); +#endif + #ifdef DEBUG_ENABLED for (const GDScriptWarning &warning : parser.get_warnings()) { if (EngineDebugger::is_active()) { @@ -914,6 +811,19 @@ Error GDScript::reload(bool p_keep_state) { } #endif + if (can_run) { + err = _static_init(); + if (err) { + return err; + } + } + +#ifdef TOOLS_ENABLED + if (can_run && p_keep_state) { + _restore_old_static_data(); + } +#endif + reloading = false; return OK; } @@ -942,6 +852,10 @@ const Variant GDScript::get_rpc_config() const { return rpc_config; } +void GDScript::unload_static() const { + GDScriptCache::remove_script(fully_qualified_name); +} + Variant GDScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { GDScript *top = this; while (top) { @@ -978,6 +892,19 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const { return true; } } + + { + HashMap<StringName, MemberInfo>::ConstIterator E = static_variables_indices.find(p_name); + if (E) { + if (E->value.getter) { + Callable::CallError ce; + r_ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce); + return true; + } + r_ret = static_variables[E->value.index]; + return true; + } + } top = top->_base; } @@ -995,7 +922,32 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) { set_source_code(p_value); reload(); } else { - return false; + const GDScript *top = this; + while (top) { + HashMap<StringName, MemberInfo>::ConstIterator E = static_variables_indices.find(p_name); + if (E) { + const GDScript::MemberInfo *member = &E->value; + Variant value = p_value; + if (member->data_type.has_type && !member->data_type.is_type(value)) { + const Variant *args = &p_value; + Callable::CallError err; + Variant::construct(member->data_type.builtin_type, value, &args, 1, err); + if (err.error != Callable::CallError::CALL_OK || !member->data_type.is_type(value)) { + return false; + } + } + if (member->setter) { + const Variant *args = &value; + Callable::CallError err; + callp(member->setter, &args, 1, err); + return err.error == Callable::CallError::CALL_OK; + } else { + static_variables.write[member->index] = value; + return true; + } + } + top = top->_base; + } } return true; @@ -1266,7 +1218,6 @@ void GDScript::_get_script_signal_list(List<MethodInfo> *r_list, bool p_include_ else if (base_cache.is_valid()) { base_cache->get_script_signal_list(r_list); } - #endif } @@ -1448,6 +1399,13 @@ void GDScript::clear(GDScript::ClearData *p_clear_data) { E.value.data_type.script_type_ref = Ref<Script>(); } + for (KeyValue<StringName, GDScript::MemberInfo> &E : static_variables_indices) { + clear_data->scripts.insert(E.value.data_type.script_type_ref); + E.value.data_type.script_type_ref = Ref<Script>(); + } + static_variables.clear(); + static_variables_indices.clear(); + if (implicit_initializer) { clear_data->functions.insert(implicit_initializer); implicit_initializer = nullptr; @@ -1458,6 +1416,11 @@ void GDScript::clear(GDScript::ClearData *p_clear_data) { implicit_ready = nullptr; } + if (static_initializer) { + clear_data->functions.insert(static_initializer); + static_initializer = nullptr; + } + _save_orphaned_subclasses(clear_data); #ifdef TOOLS_ENABLED @@ -1512,10 +1475,6 @@ GDScript::~GDScript() { GDScriptLanguage::get_singleton()->script_list.remove(&script_list); } - - if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown. - GDScriptCache::remove_script(get_path()); - } } ////////////////////////////// @@ -2546,6 +2505,7 @@ GDScriptLanguage::GDScriptLanguage() { ERR_FAIL_COND(singleton); singleton = this; strings._init = StaticCString::create("_init"); + strings._static_init = StaticCString::create("_static_init"); strings._notification = StaticCString::create("_notification"); strings._set = StaticCString::create("_set"); strings._get = StaticCString::create("_get"); diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 0117ed40ab..60bd9eef53 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -83,6 +83,7 @@ class GDScript : public Script { friend class GDScriptFunction; friend class GDScriptAnalyzer; friend class GDScriptCompiler; + friend class GDScriptDocGen; friend class GDScriptLanguage; friend struct GDScriptUtilityFunctionsDefinitions; @@ -93,6 +94,8 @@ class GDScript : public Script { HashSet<StringName> members; //members are just indices to the instantiated script. HashMap<StringName, Variant> constants; + HashMap<StringName, MemberInfo> static_variables_indices; + Vector<Variant> static_variables; HashMap<StringName, GDScriptFunction *> member_functions; HashMap<StringName, MemberInfo> member_indices; //members are just indices to the instantiated script. HashMap<StringName, Ref<GDScript>> subclasses; @@ -101,6 +104,12 @@ class GDScript : public Script { #ifdef TOOLS_ENABLED + // For static data storage during hot-reloading. + HashMap<StringName, MemberInfo> old_static_variables_indices; + Vector<Variant> old_static_variables; + void _save_old_static_data(); + void _restore_old_static_data(); + HashMap<StringName, int> member_lines; HashMap<StringName, Variant> member_default_values; List<PropertyInfo> members_cache; @@ -113,16 +122,7 @@ class GDScript : public Script { DocData::ClassDoc doc; Vector<DocData::ClassDoc> docs; - String doc_brief_description; - String doc_description; - Vector<DocData::TutorialDoc> doc_tutorials; - HashMap<String, String> doc_functions; - HashMap<String, String> doc_variables; - HashMap<String, String> doc_constants; - HashMap<String, String> doc_signals; - HashMap<String, DocData::EnumDoc> doc_enums; void _clear_doc(); - void _update_doc(); void _add_doc(const DocData::ClassDoc &p_inner_class); #endif @@ -131,6 +131,9 @@ class GDScript : public Script { GDScriptFunction *implicit_initializer = nullptr; GDScriptFunction *initializer = nullptr; //direct pointer to new , faster to locate GDScriptFunction *implicit_ready = nullptr; + GDScriptFunction *static_initializer = nullptr; + + Error _static_init(); int subclass_count = 0; RBSet<Object *> instances; @@ -276,6 +279,8 @@ public: virtual const Variant get_rpc_config() const override; + void unload_static() const; + #ifdef TOOLS_ENABLED virtual bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; } #endif @@ -447,6 +452,7 @@ public: struct { StringName _init; + StringName _static_init; StringName _notification; StringName _set; StringName _get; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index a2cab25ce8..96bd8aafad 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -479,7 +479,24 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c } if (look_class->has_member(name)) { resolve_class_member(look_class, name, id); - base = look_class->get_member(name).get_datatype(); + GDScriptParser::ClassNode::Member member = look_class->get_member(name); + GDScriptParser::DataType member_datatype = member.get_datatype(); + + switch (member.type) { + case GDScriptParser::ClassNode::Member::CLASS: + break; // OK. + case GDScriptParser::ClassNode::Member::CONSTANT: + if (member_datatype.kind != GDScriptParser::DataType::SCRIPT && member_datatype.kind != GDScriptParser::DataType::CLASS) { + push_error(vformat(R"(Constant "%s" is not a preloaded script or class.)", name), id); + return ERR_PARSE_ERROR; + } + break; + default: + push_error(vformat(R"(Cannot use %s "%s" in extends chain.)", member.get_type_name(), name), id); + return ERR_PARSE_ERROR; + } + + base = member_datatype; found = true; break; } @@ -506,6 +523,9 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c if (!id_type.is_set()) { push_error(vformat(R"(Could not find nested type "%s".)", id->name), id); return ERR_PARSE_ERROR; + } else if (id_type.kind != GDScriptParser::DataType::SCRIPT && id_type.kind != GDScriptParser::DataType::CLASS) { + push_error(vformat(R"(Identifier "%s" is not a preloaded script or class.)", id->name), id); + return ERR_PARSE_ERROR; } base = id_type; @@ -859,6 +879,8 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, #endif switch (member.type) { case GDScriptParser::ClassNode::Member::VARIABLE: { + bool previous_static_context = static_context; + static_context = member.variable->is_static; check_class_member_name_conflict(p_class, member.variable->identifier->name, member.variable); member.variable->set_datatype(resolving_datatype); resolve_variable(member.variable, false); @@ -870,6 +892,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, E->apply(parser, member.variable); } } + static_context = previous_static_context; #ifdef DEBUG_ENABLED if (member.variable->exported && member.variable->onready) { parser->push_warning(member.variable, GDScriptWarning::ONREADY_WITH_EXPORT); @@ -877,7 +900,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, if (member.variable->initializer) { // Check if it is call to get_node() on self (using shorthand $ or not), so we can check if @onready is needed. // This could be improved by traversing the expression fully and checking the presence of get_node at any level. - if (!member.variable->onready && member.variable->initializer && (member.variable->initializer->type == GDScriptParser::Node::GET_NODE || member.variable->initializer->type == GDScriptParser::Node::CALL || member.variable->initializer->type == GDScriptParser::Node::CAST)) { + if (!member.variable->is_static && !member.variable->onready && member.variable->initializer && (member.variable->initializer->type == GDScriptParser::Node::GET_NODE || member.variable->initializer->type == GDScriptParser::Node::CALL || member.variable->initializer->type == GDScriptParser::Node::CAST)) { GDScriptParser::Node *expr = member.variable->initializer; if (expr->type == GDScriptParser::Node::CAST) { expr = static_cast<GDScriptParser::CastNode *>(expr)->operand; @@ -1062,6 +1085,10 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas p_source = p_class; } +#ifdef DEBUG_ENABLED + bool has_static_data = p_class->has_static_data; +#endif + if (!p_class->resolved_interface) { if (!parser->has_class(p_class)) { String script_path = p_class->get_datatype().script_path; @@ -1104,7 +1131,29 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas for (int i = 0; i < p_class->members.size(); i++) { resolve_class_member(p_class, i); + +#ifdef DEBUG_ENABLED + if (!has_static_data) { + GDScriptParser::ClassNode::Member member = p_class->members[i]; + if (member.type == GDScriptParser::ClassNode::Member::CLASS) { + has_static_data = member.m_class->has_static_data; + } + } +#endif + } + +#ifdef DEBUG_ENABLED + if (!has_static_data && p_class->annotated_static_unload) { + GDScriptParser::Node *static_unload = nullptr; + for (GDScriptParser::AnnotationNode *node : p_class->annotations) { + if (node->name == "@static_unload") { + static_unload = node; + break; + } + } + parser->push_warning(static_unload ? static_unload : p_class, GDScriptWarning::REDUNDANT_STATIC_UNLOAD); } +#endif } } @@ -1287,10 +1336,11 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co push_error(vformat(R"(Getter with type "%s" cannot be used along with setter of type "%s".)", getter_function->datatype.to_string(), setter_function->parameters[0]->datatype.to_string()), member.variable); } } + } + #ifdef DEBUG_ENABLED - parser->ignored_warnings = previously_ignored_warnings; + parser->ignored_warnings = previously_ignored_warnings; #endif // DEBUG_ENABLED - } } } @@ -1479,6 +1529,8 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * GDScriptParser::FunctionNode *previous_function = parser->current_function; parser->current_function = p_function; + bool previous_static_context = static_context; + static_context = p_function->is_static; GDScriptParser::DataType prev_datatype = p_function->get_datatype(); @@ -1494,7 +1546,11 @@ 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, function_name, p_function->parameters[i]->identifier->name); + String visible_name = function_name; + if (function_name == StringName()) { + visible_name = p_is_lambda ? "<anonymous lambda>" : "<unknown function>"; + } + parser->push_warning(p_function->parameters[i]->identifier, GDScriptWarning::UNUSED_PARAMETER, visible_name, p_function->parameters[i]->identifier->name); } is_shadowing(p_function->parameters[i]->identifier, "function parameter"); #endif // DEBUG_ENABLED @@ -1522,6 +1578,18 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * push_error("Constructor cannot have an explicit return type.", p_function->return_type); } } + } else if (!p_is_lambda && function_name == GDScriptLanguage::get_singleton()->strings._static_init) { + // Static constructor. + GDScriptParser::DataType return_type; + return_type.kind = GDScriptParser::DataType::BUILTIN; + return_type.builtin_type = Variant::NIL; + p_function->set_datatype(return_type); + if (p_function->return_type) { + GDScriptParser::DataType declared_return = resolve_datatype(p_function->return_type); + if (declared_return.kind != GDScriptParser::DataType::BUILTIN || declared_return.builtin_type != Variant::NIL) { + push_error("Static constructor cannot have an explicit return type.", p_function->return_type); + } + } } else { if (p_function->return_type != nullptr) { p_function->set_datatype(type_from_metatype(resolve_datatype(p_function->return_type))); @@ -1605,6 +1673,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * parser->ignored_warnings = previously_ignored_warnings; #endif parser->current_function = previous_function; + static_context = previous_static_context; } void GDScriptAnalyzer::resolve_function_body(GDScriptParser::FunctionNode *p_function, bool p_is_lambda) { @@ -2007,7 +2076,7 @@ void GDScriptAnalyzer::resolve_assert(GDScriptParser::AssertNode *p_assert) { if (p_assert->condition->is_constant) { if (p_assert->condition->reduced_value.booleanize()) { parser->push_warning(p_assert->condition, GDScriptWarning::ASSERT_ALWAYS_TRUE); - } else { + } else if (!(p_assert->condition->type == GDScriptParser::Node::LITERAL && static_cast<GDScriptParser::LiteralNode *>(p_assert->condition)->value.get_type() == Variant::BOOL)) { parser->push_warning(p_assert->condition, GDScriptWarning::ASSERT_ALWAYS_FALSE); } } @@ -3030,13 +3099,17 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a base_type.is_meta_type = false; } - if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !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; + if (is_self && static_context && !is_static) { + if (parser->current_function) { + // Get the parent function above any lambda. + GDScriptParser::FunctionNode *parent_function = parser->current_function; + while (parent_function->source_lambda) { + parent_function = parent_function->source_lambda->parent_function; + } + push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parent_function->identifier->name), p_call); + } else { + push_error(vformat(R"*(Cannot call non-static function "%s()" for static variable initializer.)*", p_call->function_name), p_call); } - push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parent_function->identifier->name), p_call); } else if (!is_self && base_type.is_meta_type && !is_static) { base_type.is_meta_type = false; // For `to_string()`. push_error(vformat(R"*(Cannot call non-static function "%s()" on the class "%s" directly. Make an instance instead.)*", p_call->function_name, base_type.to_string()), p_call); @@ -3053,7 +3126,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a parser->push_warning(p_call, GDScriptWarning::RETURN_VALUE_DISCARDED, p_call->function_name); } - if (is_static && !base_type.is_meta_type && !(is_self && parser->current_function != nullptr && parser->current_function->is_static)) { + if (is_static && !is_constructor && !base_type.is_meta_type && !(is_self && static_context)) { String caller_type = String(base_type.native_type); if (caller_type.is_empty()) { @@ -3105,7 +3178,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(); #ifdef SUGGEST_GODOT4_RENAMES String rename_hint = String(); - if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) { + if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_HINT)).booleanize()) { const char *renamed_function_name = check_for_renamed_identifier(p_call->function_name, p_call->type); if (renamed_function_name) { rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", String(renamed_function_name) + "()"); @@ -3306,7 +3379,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } else if (base.is_hard_type()) { #ifdef SUGGEST_GODOT4_RENAMES String rename_hint = String(); - if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) { + if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_HINT)).booleanize()) { const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type); if (renamed_identifier_name) { rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name); @@ -3346,7 +3419,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod if (base.is_hard_type()) { #ifdef SUGGEST_GODOT4_RENAMES String rename_hint = String(); - if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) { + if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_HINT)).booleanize()) { const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type); if (renamed_identifier_name) { rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name); @@ -3408,9 +3481,9 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } case GDScriptParser::ClassNode::Member::VARIABLE: { - if (is_base && !base.is_meta_type) { + if (is_base && (!base.is_meta_type || member.variable->is_static)) { p_identifier->set_datatype(member.get_datatype()); - p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_VARIABLE; + p_identifier->source = member.variable->is_static ? GDScriptParser::IdentifierNode::STATIC_VARIABLE : GDScriptParser::IdentifierNode::MEMBER_VARIABLE; p_identifier->variable_source = member.variable; member.variable->usages += 1; return; @@ -3552,6 +3625,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident mark_lambda_use_self(); p_identifier->variable_source->usages++; [[fallthrough]]; + case GDScriptParser::IdentifierNode::STATIC_VARIABLE: case GDScriptParser::IdentifierNode::LOCAL_VARIABLE: p_identifier->set_datatype(p_identifier->variable_source->get_datatype()); found_source = true; @@ -3582,13 +3656,17 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident if (found_source) { 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; + if ((source_is_variable || source_is_signal) && static_context) { + if (parser->current_function) { + // Get the parent function above any lambda. + GDScriptParser::FunctionNode *parent_function = parser->current_function; + while (parent_function->source_lambda) { + parent_function = parent_function->source_lambda->parent_function; + } + push_error(vformat(R"*(Cannot access %s "%s" from the static function "%s()".)*", source_is_signal ? "signal" : "instance variable", p_identifier->name, parent_function->identifier->name), p_identifier); + } else { + push_error(vformat(R"*(Cannot access %s "%s" for a static variable initializer.)*", source_is_signal ? "signal" : "instance variable", p_identifier->name), p_identifier); } - push_error(vformat(R"*(Cannot access %s "%s" from the static function "%s()".)*", source_is_signal ? "signal" : "instance variable", p_identifier->name, parent_function->identifier->name), p_identifier); } if (!lambda_stack.is_empty()) { @@ -3730,7 +3808,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident } else { #ifdef SUGGEST_GODOT4_RENAMES String rename_hint = String(); - if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) { + if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GODOT_4_HINT)).booleanize()) { const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type); if (renamed_identifier_name) { rename_hint = " " + vformat(R"(Did you mean to use "%s"?)", renamed_identifier_name); @@ -4546,7 +4624,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo result.set_container_element_type(elem_type); } else if (p_property.type == Variant::INT) { // Check if it's enum. - if ((p_property.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) && p_property.class_name != StringName()) { + if ((p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && p_property.class_name != StringName()) { if (CoreConstants::is_global_enum(p_property.class_name)) { result = make_global_enum_type(p_property.class_name, StringName(), false); result.is_constant = false; @@ -4558,6 +4636,7 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo } } } + // PROPERTY_USAGE_CLASS_IS_BITFIELD: BitField[T] isn't supported (yet?), use plain int. } } return result; @@ -4758,9 +4837,11 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p } GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype(); - if ((arg_type.is_variant() || !arg_type.is_hard_type()) && !(par_type.is_hard_type() && par_type.is_variant())) { - // Argument can be anything, so this is unsafe. - mark_node_unsafe(p_call->arguments[i]); + if (arg_type.is_variant() || !arg_type.is_hard_type()) { + // Argument can be anything, so this is unsafe (unless the parameter is a hard variant). + if (!(par_type.is_hard_type() && par_type.is_variant())) { + mark_node_unsafe(p_call->arguments[i]); + } } else if (par_type.is_hard_type() && !is_type_compatible(par_type, arg_type, true)) { // Supertypes are acceptable for dynamic compliance, but it's unsafe. mark_node_unsafe(p_call); diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 5902035bcd..0c7bf4125b 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -43,6 +43,7 @@ class GDScriptAnalyzer { const GDScriptParser::EnumNode *current_enum = nullptr; List<GDScriptParser::LambdaNode *> lambda_stack; + bool static_context = false; // 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, const GDScriptParser::Node *p_member); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index d6f21d297a..1414075ba8 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -69,56 +69,52 @@ uint32_t GDScriptByteCodeGenerator::add_or_get_name(const StringName &p_name) { uint32_t GDScriptByteCodeGenerator::add_temporary(const GDScriptDataType &p_type) { Variant::Type temp_type = Variant::NIL; - if (p_type.has_type) { - if (p_type.kind == GDScriptDataType::BUILTIN) { - switch (p_type.builtin_type) { - case Variant::NIL: - case Variant::BOOL: - case Variant::INT: - case Variant::FLOAT: - case Variant::STRING: - case Variant::VECTOR2: - case Variant::VECTOR2I: - case Variant::RECT2: - case Variant::RECT2I: - case Variant::VECTOR3: - case Variant::VECTOR3I: - case Variant::TRANSFORM2D: - case Variant::VECTOR4: - case Variant::VECTOR4I: - case Variant::PLANE: - case Variant::QUATERNION: - case Variant::AABB: - case Variant::BASIS: - case Variant::TRANSFORM3D: - case Variant::PROJECTION: - case Variant::COLOR: - case Variant::STRING_NAME: - case Variant::NODE_PATH: - case Variant::RID: - case Variant::OBJECT: - case Variant::CALLABLE: - case Variant::SIGNAL: - case Variant::DICTIONARY: - case Variant::ARRAY: - temp_type = p_type.builtin_type; - break; - case Variant::PACKED_BYTE_ARRAY: - case Variant::PACKED_INT32_ARRAY: - case Variant::PACKED_INT64_ARRAY: - case Variant::PACKED_FLOAT32_ARRAY: - case Variant::PACKED_FLOAT64_ARRAY: - case Variant::PACKED_STRING_ARRAY: - case Variant::PACKED_VECTOR2_ARRAY: - case Variant::PACKED_VECTOR3_ARRAY: - case Variant::PACKED_COLOR_ARRAY: - case Variant::VARIANT_MAX: - // Packed arrays are reference counted, so we don't use the pool for them. - temp_type = Variant::NIL; - break; - } - } else { - temp_type = Variant::OBJECT; + if (p_type.has_type && p_type.kind == GDScriptDataType::BUILTIN) { + switch (p_type.builtin_type) { + case Variant::NIL: + case Variant::BOOL: + case Variant::INT: + case Variant::FLOAT: + case Variant::STRING: + case Variant::VECTOR2: + case Variant::VECTOR2I: + case Variant::RECT2: + case Variant::RECT2I: + case Variant::VECTOR3: + case Variant::VECTOR3I: + case Variant::TRANSFORM2D: + case Variant::VECTOR4: + case Variant::VECTOR4I: + case Variant::PLANE: + case Variant::QUATERNION: + case Variant::AABB: + case Variant::BASIS: + case Variant::TRANSFORM3D: + case Variant::PROJECTION: + case Variant::COLOR: + case Variant::STRING_NAME: + case Variant::NODE_PATH: + case Variant::RID: + case Variant::CALLABLE: + case Variant::SIGNAL: + temp_type = p_type.builtin_type; + break; + case Variant::OBJECT: + case Variant::DICTIONARY: + case Variant::ARRAY: + case Variant::PACKED_BYTE_ARRAY: + case Variant::PACKED_INT32_ARRAY: + case Variant::PACKED_INT64_ARRAY: + case Variant::PACKED_FLOAT32_ARRAY: + case Variant::PACKED_FLOAT64_ARRAY: + case Variant::PACKED_STRING_ARRAY: + case Variant::PACKED_VECTOR2_ARRAY: + case Variant::PACKED_VECTOR3_ARRAY: + case Variant::PACKED_COLOR_ARRAY: + case Variant::VARIANT_MAX: + // Arrays, dictionaries, and objects are reference counted, so we don't use the pool for them. + temp_type = Variant::NIL; + break; } } @@ -143,10 +139,12 @@ void GDScriptByteCodeGenerator::pop_temporary() { ERR_FAIL_COND(used_temporaries.is_empty()); int slot_idx = used_temporaries.back()->get(); const StackSlot &slot = temporaries[slot_idx]; - if (slot.type == Variant::OBJECT) { + if (slot.type == Variant::NIL) { // Avoid keeping in the stack long-lived references to objects, // which may prevent RefCounted objects from being freed. - write_assign_false(Address(Address::TEMPORARY, slot_idx)); + // However, the cleanup will be performed an the end of the + // statement, to allow object references to survive chaining. + temporaries_pending_clear.push_back(slot_idx); } temporaries_pool[slot.type].push_back(slot_idx); used_temporaries.pop_back(); @@ -1756,6 +1754,23 @@ void GDScriptByteCodeGenerator::end_block() { pop_stack_identifiers(); } +void GDScriptByteCodeGenerator::clean_temporaries() { + List<int>::Element *E = temporaries_pending_clear.front(); + while (E) { + // The temporary may have been re-used as something else than an object + // since it was added to the list. In that case, there's no need to clear it. + int slot_idx = E->get(); + const StackSlot &slot = temporaries[slot_idx]; + if (slot.type == Variant::NIL) { + write_assign_false(Address(Address::TEMPORARY, slot_idx)); + } + + List<int>::Element *next = E->next(); + E->erase(); + E = next; + } +} + GDScriptByteCodeGenerator::~GDScriptByteCodeGenerator() { if (!ended && function != nullptr) { memdelete(function); diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index dc05de9fc6..fc684e4d8f 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -88,6 +88,7 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { Vector<StackSlot> locals; Vector<StackSlot> temporaries; List<int> used_temporaries; + List<int> temporaries_pending_clear; RBMap<Variant::Type, List<int>> temporaries_pool; List<GDScriptFunction::StackDebug> stack_debug; @@ -365,6 +366,8 @@ class GDScriptByteCodeGenerator : public GDScriptCodeGenerator { return p_address.address | (GDScriptFunction::ADDR_TYPE_MEMBER << GDScriptFunction::ADDR_BITS); case Address::CONSTANT: return p_address.address | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS); + case Address::STATIC_VARIABLE: + return p_address.address | (GDScriptFunction::ADDR_TYPE_STATIC_VAR << GDScriptFunction::ADDR_BITS); case Address::LOCAL_VARIABLE: case Address::FUNCTION_PARAMETER: return p_address.address | (GDScriptFunction::ADDR_TYPE_STACK << GDScriptFunction::ADDR_BITS); @@ -463,6 +466,7 @@ public: virtual uint32_t add_or_get_name(const StringName &p_name) override; virtual uint32_t add_temporary(const GDScriptDataType &p_type) override; virtual void pop_temporary() override; + virtual void clean_temporaries() override; virtual void start_parameters() override; virtual void end_parameters() override; diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index a009e8e0a8..126fccbbf0 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -342,6 +342,16 @@ Error GDScriptCache::finish_compiling(const String &p_owner) { return err; } +void GDScriptCache::add_static_script(Ref<GDScript> p_script) { + ERR_FAIL_COND_MSG(p_script.is_null(), "Trying to cache empty script as static."); + ERR_FAIL_COND_MSG(!p_script->is_valid(), "Trying to cache non-compiled script as static."); + singleton->static_gdscript_cache[p_script->get_fully_qualified_name()] = p_script; +} + +void GDScriptCache::remove_static_script(const String &p_fqcn) { + singleton->static_gdscript_cache.erase(p_fqcn); +} + Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_error, const String &p_owner) { MutexLock lock(singleton->mutex); diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index c7f40f6e82..28266a1c0b 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -78,6 +78,7 @@ class GDScriptCache { HashMap<String, GDScriptParserRef *> parser_map; HashMap<String, Ref<GDScript>> shallow_gdscript_cache; HashMap<String, Ref<GDScript>> full_gdscript_cache; + HashMap<String, Ref<GDScript>> static_gdscript_cache; HashMap<String, HashSet<String>> dependencies; HashMap<String, Ref<PackedScene>> packed_scene_cache; HashMap<String, HashSet<String>> packed_scene_dependencies; @@ -101,6 +102,8 @@ public: 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 void add_static_script(Ref<GDScript> p_script); + static void remove_static_script(const String &p_fqcn); static Ref<PackedScene> get_packed_scene(const String &p_path, Error &r_error, const String &p_owner = ""); static void clear_unreferenced_packed_scenes(); diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index 7847ab28c7..dbc2466393 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -44,6 +44,7 @@ public: CLASS, MEMBER, CONSTANT, + STATIC_VARIABLE, LOCAL_VARIABLE, FUNCTION_PARAMETER, TEMPORARY, @@ -72,6 +73,7 @@ public: virtual uint32_t add_or_get_name(const StringName &p_name) = 0; virtual uint32_t add_temporary(const GDScriptDataType &p_type) = 0; virtual void pop_temporary() = 0; + virtual void clean_temporaries() = 0; virtual void start_parameters() = 0; virtual void end_parameters() = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 5413eadf60..327e24ef11 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -254,13 +254,29 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code gen->write_call_self(temp, codegen.script->member_indices[identifier].getter, args); return temp; } else { - // No getter or inside getter: direct member access., + // No getter or inside getter: direct member access. int idx = codegen.script->member_indices[identifier].index; return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, idx, codegen.script->get_member_type(identifier)); } } } + // Try static variables. + if (codegen.script->static_variables_indices.has(identifier)) { + if (codegen.script->static_variables_indices[identifier].getter != StringName() && codegen.script->static_variables_indices[identifier].getter != codegen.function_name) { + // Perform getter. + GDScriptCodeGenerator::Address temp = codegen.add_temporary(codegen.script->static_variables_indices[identifier].data_type); + GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS); + Vector<GDScriptCodeGenerator::Address> args; // No argument needed. + gen->write_call(temp, class_addr, codegen.script->static_variables_indices[identifier].getter, args); + return temp; + } else { + // No getter or inside getter: direct variable access. + int idx = codegen.script->static_variables_indices[identifier].index; + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::STATIC_VARIABLE, idx, codegen.script->static_variables_indices[identifier].data_type); + } + } + // Try class constants. { GDScript *owner = codegen.script; @@ -563,7 +579,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Not exact arguments, but still can use method bind call. gen->write_call_method_bind(result, self, method, arguments); } - } else if ((codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") { + } else if (codegen.is_static || (codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") { GDScriptCodeGenerator::Address self; self.mode = GDScriptCodeGenerator::Address::CLASS; if (is_awaited) { @@ -909,6 +925,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code bool is_member_property = false; bool member_property_has_setter = false; bool member_property_is_in_setter = false; + bool is_static = false; StringName member_property_setter_function; List<const GDScriptParser::SubscriptNode *> chain; @@ -925,14 +942,16 @@ 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 (!_is_local_or_parameter(codegen, 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) || codegen.script->static_variables_indices.has(var_name))) { is_member_property = true; - member_property_setter_function = codegen.script->member_indices[var_name].setter; + is_static = codegen.script->static_variables_indices.has(var_name); + const GDScript::MemberInfo &minfo = is_static ? codegen.script->static_variables_indices[var_name] : codegen.script->member_indices[var_name]; + member_property_setter_function = minfo.setter; member_property_has_setter = member_property_setter_function != StringName(); member_property_is_in_setter = member_property_has_setter && member_property_setter_function == codegen.function_name; - target_member_property.mode = GDScriptCodeGenerator::Address::MEMBER; - target_member_property.address = codegen.script->member_indices[var_name].index; - target_member_property.type = codegen.script->member_indices[var_name].data_type; + target_member_property.mode = is_static ? GDScriptCodeGenerator::Address::STATIC_VARIABLE : GDScriptCodeGenerator::Address::MEMBER; + target_member_property.address = minfo.index; + target_member_property.type = minfo.data_type; } } break; @@ -1085,7 +1104,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code if (member_property_has_setter && !member_property_is_in_setter) { Vector<GDScriptCodeGenerator::Address> args; args.push_back(assigned); - gen->write_call(GDScriptCodeGenerator::Address(), GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), member_property_setter_function, args); + GDScriptCodeGenerator::Address self = is_static ? GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::CLASS) : GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF); + gen->write_call(GDScriptCodeGenerator::Address(), self, member_property_setter_function, args); } else { gen->write_assign(target_member_property, assigned); } @@ -1134,16 +1154,19 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code bool is_member = false; bool has_setter = false; bool is_in_setter = false; + bool is_static = false; StringName setter_function; StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name; - if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) { + if (!_is_local_or_parameter(codegen, var_name) && (codegen.script->member_indices.has(var_name) || codegen.script->static_variables_indices.has(var_name))) { is_member = true; - setter_function = codegen.script->member_indices[var_name].setter; + is_static = codegen.script->static_variables_indices.has(var_name); + GDScript::MemberInfo &minfo = is_static ? codegen.script->static_variables_indices[var_name] : codegen.script->member_indices[var_name]; + setter_function = minfo.setter; has_setter = setter_function != StringName(); is_in_setter = has_setter && setter_function == codegen.function_name; - member.mode = GDScriptCodeGenerator::Address::MEMBER; - member.address = codegen.script->member_indices[var_name].index; - member.type = codegen.script->member_indices[var_name].data_type; + member.mode = is_static ? GDScriptCodeGenerator::Address::STATIC_VARIABLE : GDScriptCodeGenerator::Address::MEMBER; + member.address = minfo.index; + member.type = minfo.data_type; } GDScriptCodeGenerator::Address target; @@ -1165,18 +1188,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code bool has_operation = assignment->operation != GDScriptParser::AssignmentNode::OP_NONE; if (has_operation) { // Perform operation. - GDScriptCodeGenerator::Address og_value = _parse_expression(codegen, r_error, assignment->assignee); - - if (!has_setter && !assignment->use_conversion_assign) { - // If there's nothing special about the assignment, perform the assignment as part of the operator - gen->write_binary_operator(target, assignment->variant_op, og_value, assigned_value); - if (assigned_value.mode == GDScriptCodeGenerator::Address::TEMPORARY) { - gen->pop_temporary(); // Pop assigned value if not done before. - } - return GDScriptCodeGenerator::Address(); - } - 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; @@ -1665,6 +1678,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui Error err = OK; GDScriptCodeGenerator *gen = codegen.generator; + gen->clean_temporaries(); codegen.start_block(); if (p_add_locals) { @@ -1967,6 +1981,8 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui } } break; } + + gen->clean_temporaries(); } codegen.end_block(); @@ -2008,6 +2024,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ } codegen.function_name = func_name; + codegen.is_static = is_static; codegen.generator->write_start(p_script, func_name, is_static, rpc_config, return_type); int optional_parameters = 0; @@ -2031,7 +2048,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ bool is_implicit_ready = !p_func && p_for_ready; if (!p_for_lambda && is_implicit_initializer) { - // Initialize the default values for type variables before anything. + // Initialize the default values for typed variables before anything. // This avoids crashes if they are accessed with validated calls before being properly initialized. // It may happen with out-of-order access or with `@onready` variables. for (const GDScriptParser::ClassNode::Member &member : p_class->members) { @@ -2040,6 +2057,10 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ } const GDScriptParser::VariableNode *field = member.variable; + if (field->is_static) { + continue; + } + 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, field_type); @@ -2063,6 +2084,10 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ continue; } const GDScriptParser::VariableNode *field = p_class->members[i].variable; + if (field->is_static) { + continue; + } + if (field->onready != is_implicit_ready) { // Only initialize in @implicit_ready. continue; @@ -2152,12 +2177,6 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ if (p_func) { codegen.generator->set_initial_line(p_func->start_line); -#ifdef TOOLS_ENABLED - if (!p_for_lambda) { - p_script->member_lines[func_name] = p_func->start_line; - p_script->doc_functions[func_name] = p_func->doc_description; - } -#endif } else { codegen.generator->set_initial_line(0); } @@ -2196,6 +2215,135 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ return gd_function; } +GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class) { + r_error = OK; + CodeGen codegen; + codegen.generator = memnew(GDScriptByteCodeGenerator); + + codegen.class_node = p_class; + codegen.script = p_script; + + StringName func_name = SNAME("@static_initializer"); + bool is_static = true; + Variant rpc_config; + GDScriptDataType return_type; + return_type.has_type = true; + return_type.kind = GDScriptDataType::BUILTIN; + return_type.builtin_type = Variant::NIL; + + codegen.function_name = func_name; + codegen.is_static = is_static; + codegen.generator->write_start(p_script, func_name, is_static, rpc_config, return_type); + + GDScriptCodeGenerator::Address class_addr(GDScriptCodeGenerator::Address::CLASS); + + // Initialize the default values for typed variables before anything. + // This avoids crashes if they are accessed with validated calls before being properly initialized. + // It may happen with out-of-order access or with `@onready` variables. + for (const GDScriptParser::ClassNode::Member &member : p_class->members) { + if (member.type != GDScriptParser::ClassNode::Member::VARIABLE) { + continue; + } + + const GDScriptParser::VariableNode *field = member.variable; + if (!field->is_static) { + continue; + } + + GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script); + + if (field_type.has_type) { + codegen.generator->write_newline(field->start_line); + + if (field_type.has_container_element_type()) { + GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type); + codegen.generator->write_construct_typed_array(temp, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); + codegen.generator->write_set_named(class_addr, field->identifier->name, temp); + codegen.generator->pop_temporary(); + + } else if (field_type.kind == GDScriptDataType::BUILTIN) { + GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type); + codegen.generator->write_construct(temp, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); + codegen.generator->write_set_named(class_addr, field->identifier->name, temp); + codegen.generator->pop_temporary(); + } + // The `else` branch is for objects, in such case we leave it as `null`. + } + } + + for (int i = 0; i < p_class->members.size(); i++) { + // Initialize static fields. + if (p_class->members[i].type != GDScriptParser::ClassNode::Member::VARIABLE) { + continue; + } + const GDScriptParser::VariableNode *field = p_class->members[i].variable; + if (!field->is_static) { + continue; + } + + GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script); + + if (field->initializer) { + // Emit proper line change. + codegen.generator->write_newline(field->initializer->start_line); + + GDScriptCodeGenerator::Address src_address = _parse_expression(codegen, r_error, field->initializer, false, true); + if (r_error) { + memdelete(codegen.generator); + return nullptr; + } + + GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type); + if (field->use_conversion_assign) { + codegen.generator->write_assign_with_conversion(temp, src_address); + } else { + codegen.generator->write_assign(temp, src_address); + } + if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + codegen.generator->pop_temporary(); + } + + codegen.generator->write_set_named(class_addr, field->identifier->name, temp); + codegen.generator->pop_temporary(); + } + } + + if (p_script->has_method(GDScriptLanguage::get_singleton()->strings._static_init)) { + codegen.generator->write_newline(p_class->start_line); + codegen.generator->write_call(GDScriptCodeGenerator::Address(), class_addr, GDScriptLanguage::get_singleton()->strings._static_init, Vector<GDScriptCodeGenerator::Address>()); + } + +#ifdef DEBUG_ENABLED + if (EngineDebugger::is_active()) { + String signature; + // Path. + if (!p_script->get_script_path().is_empty()) { + signature += p_script->get_script_path(); + } + // Location. + signature += "::0"; + + // Function and class. + + if (p_class->identifier) { + signature += "::" + String(p_class->identifier->name) + "." + String(func_name); + } else { + signature += "::" + String(func_name); + } + + codegen.generator->set_signature(signature); + } +#endif + + codegen.generator->set_initial_line(p_class->start_line); + + GDScriptFunction *gd_function = codegen.generator->write_end(); + + memdelete(codegen.generator); + + return gd_function; +} + Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter) { Error err = OK; @@ -2226,23 +2374,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri parsing_classes.insert(p_script); p_script->clearing = true; -#ifdef TOOLS_ENABLED - p_script->doc_functions.clear(); - p_script->doc_variables.clear(); - p_script->doc_constants.clear(); - p_script->doc_enums.clear(); - p_script->doc_signals.clear(); - p_script->doc_tutorials.clear(); - - p_script->doc_brief_description = p_class->doc_brief_description; - p_script->doc_description = p_class->doc_description; - for (int i = 0; i < p_class->doc_tutorials.size(); i++) { - DocData::TutorialDoc td; - td.title = p_class->doc_tutorials[i].first; - td.link = p_class->doc_tutorials[i].second; - p_script->doc_tutorials.append(td); - } -#endif p_script->native = Ref<GDScriptNativeClass>(); p_script->base = Ref<GDScript>(); @@ -2266,19 +2397,28 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri } member_functions.clear(); + p_script->static_variables.clear(); + if (p_script->implicit_initializer) { memdelete(p_script->implicit_initializer); } if (p_script->implicit_ready) { memdelete(p_script->implicit_ready); } + if (p_script->static_initializer) { + memdelete(p_script->static_initializer); + } + p_script->member_functions.clear(); p_script->member_indices.clear(); p_script->member_info.clear(); + p_script->static_variables_indices.clear(); + p_script->static_variables.clear(); p_script->_signals.clear(); p_script->initializer = nullptr; p_script->implicit_initializer = nullptr; p_script->implicit_ready = nullptr; + p_script->static_initializer = nullptr; p_script->clearing = false; @@ -2386,13 +2526,15 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri } else { prop_info.usage = PROPERTY_USAGE_SCRIPT_VARIABLE; } -#ifdef TOOLS_ENABLED - p_script->doc_variables[name] = variable->doc_description; -#endif - p_script->member_info[name] = prop_info; - p_script->member_indices[name] = minfo; - p_script->members.insert(name); + if (variable->is_static) { + minfo.index = p_script->static_variables_indices.size(); + p_script->static_variables_indices[name] = minfo; + } else { + p_script->member_info[name] = prop_info; + p_script->member_indices[name] = minfo; + p_script->members.insert(name); + } #ifdef TOOLS_ENABLED if (variable->initializer != nullptr && variable->initializer->is_constant) { @@ -2401,7 +2543,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri } else { p_script->member_default_values.erase(name); } - p_script->member_lines[name] = variable->start_line; #endif } break; @@ -2410,12 +2551,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri StringName name = constant->identifier->name; p_script->constants.insert(name, constant->initializer->reduced_value); -#ifdef TOOLS_ENABLED - p_script->member_lines[name] = constant->start_line; - if (!constant->doc_description.is_empty()) { - p_script->doc_constants[name] = constant->doc_description; - } -#endif } break; case GDScriptParser::ClassNode::Member::ENUM_VALUE: { @@ -2423,18 +2558,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri StringName name = enum_value.identifier->name; p_script->constants.insert(name, enum_value.value); -#ifdef TOOLS_ENABLED - p_script->member_lines[name] = enum_value.identifier->start_line; - if (!p_script->doc_enums.has("@unnamed_enums")) { - p_script->doc_enums["@unnamed_enums"] = DocData::EnumDoc(); - p_script->doc_enums["@unnamed_enums"].name = "@unnamed_enums"; - } - DocData::ConstantDoc const_doc; - const_doc.name = enum_value.identifier->name; - const_doc.value = Variant(enum_value.value).operator String(); // TODO-DOC: enum value currently is int. - const_doc.description = enum_value.doc_description; - p_script->doc_enums["@unnamed_enums"].values.push_back(const_doc); -#endif } break; case GDScriptParser::ClassNode::Member::SIGNAL: { @@ -2447,11 +2570,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri parameters_names.write[j] = signal->parameters[j]->identifier->name; } p_script->_signals[name] = parameters_names; -#ifdef TOOLS_ENABLED - if (!signal->doc_description.is_empty()) { - p_script->doc_signals[name] = signal->doc_description; - } -#endif } break; case GDScriptParser::ClassNode::Member::ENUM: { @@ -2459,19 +2577,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri StringName name = enum_n->identifier->name; p_script->constants.insert(name, enum_n->dictionary); -#ifdef TOOLS_ENABLED - 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[name].values.push_back(const_doc); - } -#endif } break; case GDScriptParser::ClassNode::Member::GROUP: { @@ -2497,6 +2602,8 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri } } + p_script->static_variables.resize(p_script->static_variables_indices.size()); + parsed_classes.insert(p_script); parsing_classes.erase(p_script); @@ -2519,9 +2626,6 @@ Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScri } } -#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 } @@ -2576,6 +2680,15 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser: } } + if (p_class->has_static_data) { + Error err = OK; + GDScriptFunction *func = _make_static_initializer(err, p_script, p_class); + p_script->static_initializer = func; + if (err) { + return err; + } + } + #ifdef DEBUG_ENABLED //validate instances if keeping state @@ -2614,7 +2727,7 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser: //well, tough luck, not gonna do anything here } } -#endif +#endif // TOOLS_ENABLED } else { GDScriptInstance *gi = static_cast<GDScriptInstance *>(si); gi->reload_members(); @@ -2623,7 +2736,9 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser: E = N; } } -#endif +#endif //DEBUG_ENABLED + + has_static_data = p_class->has_static_data; for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type != GDScriptParser::ClassNode::Member::CLASS) { @@ -2637,6 +2752,8 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser: if (err) { return err; } + + has_static_data = has_static_data || inner_class->has_static_data; } p_script->_init_rpc_methods_properties(); @@ -2723,9 +2840,9 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri return err; } -#ifdef TOOLS_ENABLED - p_script->_update_doc(); -#endif + if (has_static_data && !root->annotated_static_unload) { + GDScriptCache::add_static_script(p_script); + } return GDScriptCache::finish_compiling(main_script->get_path()); } diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 5328c17c73..2d15d461fb 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -52,6 +52,7 @@ class GDScriptCompiler { HashMap<StringName, GDScriptCodeGenerator::Address> parameters; HashMap<StringName, GDScriptCodeGenerator::Address> locals; List<HashMap<StringName, GDScriptCodeGenerator::Address>> locals_stack; + bool is_static = false; GDScriptCodeGenerator::Address add_local(const StringName &p_name, const GDScriptDataType &p_type) { uint32_t addr = generator->add_local(p_name, p_type); @@ -130,6 +131,7 @@ class GDScriptCompiler { void _add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block); Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true); GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = false); + GDScriptFunction *_make_static_initializer(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class); Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter); Error _populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); Error _compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); @@ -138,6 +140,7 @@ class GDScriptCompiler { StringName source; String error; GDScriptParser::ExpressionNode *awaited_node = nullptr; + bool has_static_data = false; public: static void convert_to_initializer_type(Variant &p_variant, const GDScriptParser::VariableNode *p_node); diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index 0acc03be3d..45ad8792d9 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -851,7 +851,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { } text += ")"; - incr = 3 + captures_count; + incr = 4 + captures_count; } break; case OPCODE_CREATE_SELF_LAMBDA: { int instr_var_args = _code_ptr[++ip]; @@ -871,7 +871,7 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { } text += ")"; - incr = 3 + captures_count; + incr = 4 + captures_count; } break; case OPCODE_JUMP: { text += "jump "; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index f3a86522ae..09af51656c 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -54,6 +54,7 @@ void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const { p_delimiters->push_back("\" \""); p_delimiters->push_back("' '"); p_delimiters->push_back("\"\"\" \"\"\""); + p_delimiters->push_back("''' '''"); } bool GDScriptLanguage::is_using_templates() { @@ -73,9 +74,11 @@ Ref<Script> GDScriptLanguage::make_template(const String &p_template, const Stri .replace(": String", "") .replace(": Array[String]", "") .replace(": float", "") + .replace(": CharFXTransform", "") .replace(":=", "=") .replace(" -> String", "") .replace(" -> int", "") + .replace(" -> bool", "") .replace(" -> void", ""); } @@ -578,29 +581,34 @@ static int _get_enum_constant_location(StringName p_class, StringName p_enum_con // END LOCATION METHODS -static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg = true) { - if (p_info.usage & (PROPERTY_USAGE_CLASS_IS_ENUM | PROPERTY_USAGE_CLASS_IS_BITFIELD)) { - String enum_name = p_info.class_name; - if (!enum_name.contains(".")) { - return enum_name; +static String _trim_parent_class(const String &p_class, const String &p_base_class) { + if (p_base_class.is_empty()) { + return p_class; + } + Vector<String> names = p_class.split(".", false, 1); + if (names.size() == 2) { + String first = names[0]; + String rest = names[1]; + if (ClassDB::class_exists(p_base_class) && ClassDB::class_exists(first) && ClassDB::is_parent_class(p_base_class, first)) { + return rest; } - return enum_name.get_slice(".", 1); } + return p_class; +} - String n = p_info.name; - int idx = n.find(":"); - if (idx != -1) { - return n.substr(idx + 1, n.length()); - } +static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg, const String &p_base_class = "") { + String class_name = p_info.class_name; + bool is_enum = p_info.type == Variant::INT && p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM; + // PROPERTY_USAGE_CLASS_IS_BITFIELD: BitField[T] isn't supported (yet?), use plain int. - if (p_info.type == Variant::OBJECT) { - if (p_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { - return p_info.hint_string; - } else { - return p_info.class_name.operator String(); + if ((p_info.type == Variant::OBJECT || is_enum) && !class_name.is_empty()) { + if (is_enum && CoreConstants::is_global_enum(p_info.class_name)) { + return class_name; } - } - if (p_info.type == Variant::NIL) { + return _trim_parent_class(class_name, p_base_class); + } else if (p_info.type == Variant::ARRAY && p_info.hint == PROPERTY_HINT_ARRAY_TYPE && !p_info.hint_string.is_empty()) { + return "Array[" + _trim_parent_class(p_info.hint_string, p_base_class) + "]"; + } else if (p_info.type == Variant::NIL) { if (p_is_arg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) { return "Variant"; } else { @@ -898,19 +906,20 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio } } -static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result) { +static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth = 0) { for (int i = 0; i < p_suite->locals.size(); i++) { ScriptLanguage::CodeCompletionOption option; + int location = p_recursion_depth == 0 ? ScriptLanguage::LOCATION_LOCAL : (p_recursion_depth | ScriptLanguage::LOCATION_PARENT_MASK); if (p_suite->locals[i].type == GDScriptParser::SuiteNode::Local::CONSTANT) { - option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, ScriptLanguage::LOCATION_LOCAL); + option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); option.default_value = p_suite->locals[i].constant->initializer->reduced_value; } else { - option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE, ScriptLanguage::LOCATION_LOCAL); + option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE, location); } r_result.insert(option.display, option); } if (p_suite->parent_block) { - _find_identifiers_in_suite(p_suite->parent_block, r_result); + _find_identifiers_in_suite(p_suite->parent_block, r_result, p_recursion_depth + 1); } } @@ -925,7 +934,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class, int classes_processed = 0; while (clss) { for (int i = 0; i < clss->members.size(); i++) { - const int location = (classes_processed + p_recursion_depth) | ScriptLanguage::LOCATION_PARENT_MASK; + const int location = p_recursion_depth == 0 ? classes_processed : (p_recursion_depth | ScriptLanguage::LOCATION_PARENT_MASK); const GDScriptParser::ClassNode::Member &member = clss->members[i]; ScriptLanguage::CodeCompletionOption option; switch (member.type) { @@ -1017,7 +1026,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, base_type.is_meta_type, 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); // This already finds all parent identifiers, so we are done. base_type = GDScriptParser::DataType(); } break; @@ -1197,7 +1206,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context } if (p_context.current_class) { - _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth + 1); + _find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth); } List<StringName> functions; @@ -2984,6 +2993,15 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c List<MethodInfo> virtual_methods; ClassDB::get_virtual_methods(class_name, &virtual_methods); + + { + // Not truly a virtual method, but can also be "overridden". + MethodInfo static_init("_static_init"); + static_init.return_val.type = Variant::NIL; + static_init.flags |= METHOD_FLAG_STATIC | METHOD_FLAG_VIRTUAL; + virtual_methods.push_back(static_init); + } + for (const MethodInfo &mi : virtual_methods) { String method_hint = mi.name; if (method_hint.contains(":")) { @@ -3001,26 +3019,14 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c arg = arg.substr(0, arg.find(":")); } method_hint += arg; - if (use_type_hint && mi.arguments[i].type != Variant::NIL) { - method_hint += ": "; - if (mi.arguments[i].type == Variant::OBJECT && mi.arguments[i].class_name != StringName()) { - method_hint += mi.arguments[i].class_name.operator String(); - } else { - method_hint += Variant::get_type_name(mi.arguments[i].type); - } + if (use_type_hint) { + method_hint += ": " + _get_visual_datatype(mi.arguments[i], true, class_name); } } } method_hint += ")"; - if (use_type_hint && (mi.return_val.type != Variant::NIL || !(mi.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT))) { - method_hint += " -> "; - if (mi.return_val.type == Variant::NIL) { - method_hint += "void"; - } else if (mi.return_val.type == Variant::OBJECT && mi.return_val.class_name != StringName()) { - method_hint += mi.return_val.class_name.operator String(); - } else { - method_hint += Variant::get_type_name(mi.return_val.type); - } + if (use_type_hint) { + method_hint += " -> " + _get_visual_datatype(mi.return_val, false, class_name); } method_hint += ":"; @@ -3093,12 +3099,7 @@ String GDScriptLanguage::_get_indentation() const { if (use_space_indentation) { int indent_size = EDITOR_GET("text_editor/behavior/indent/size"); - - String space_indent = ""; - for (int i = 0; i < indent_size; i++) { - space_indent += " "; - } - return space_indent; + return String(" ").repeat(indent_size); } } #endif @@ -3145,12 +3146,7 @@ void GDScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_t } if (i >= p_from_line) { - l = ""; - for (int j = 0; j < indent_stack.size(); j++) { - l += indent; - } - l += st; - + l = indent.repeat(indent_stack.size()) + st; } else if (i > p_to_line) { break; } diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 1a5e9eef53..390e562e6f 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -409,7 +409,8 @@ public: ADDR_TYPE_STACK = 0, ADDR_TYPE_CONSTANT = 1, ADDR_TYPE_MEMBER = 2, - ADDR_TYPE_MAX = 3, + ADDR_TYPE_STATIC_VAR = 3, + ADDR_TYPE_MAX = 4, }; enum FixedAddresses { diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index e2a37ab6e9..d90503c658 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -81,6 +81,8 @@ GDScriptParser::GDScriptParser() { // TODO: Should this be static? register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation); register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation); + register_annotation(MethodInfo("@static_unload"), AnnotationInfo::SCRIPT, &GDScriptParser::static_unload_annotation); + register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation); // Export annotations. register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>); @@ -102,6 +104,7 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>); + register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>); // Export grouping annotations. register_annotation(MethodInfo("@export_category", PropertyInfo(Variant::STRING, "name")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_CATEGORY>); register_annotation(MethodInfo("@export_group", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "prefix")), AnnotationInfo::STANDALONE, &GDScriptParser::export_group_annotations<PROPERTY_USAGE_GROUP>, varray("")); @@ -623,7 +626,7 @@ bool GDScriptParser::has_class(const GDScriptParser::ClassNode *p_class) const { return false; } -GDScriptParser::ClassNode *GDScriptParser::parse_class() { +GDScriptParser::ClassNode *GDScriptParser::parse_class(bool p_is_static) { ClassNode *n_class = alloc_node<ClassNode>(); ClassNode *previous_class = current_class; @@ -724,7 +727,7 @@ void GDScriptParser::parse_extends() { } template <class T> -void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind) { +void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static) { advance(); #ifdef TOOLS_ENABLED @@ -749,7 +752,7 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)() #endif // TOOLS_ENABLED } - T *member = (this->*p_parse_function)(); + T *member = (this->*p_parse_function)(p_is_static); if (member == nullptr) { return; } @@ -761,7 +764,11 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)() #ifdef TOOLS_ENABLED // Consume doc comments. class_doc_line = MIN(class_doc_line, doc_comment_line - 1); - if (has_comment(doc_comment_line)) { + + // Check whether current line has a doc comment + if (has_comment(previous.start_line, true)) { + member->doc_description = get_doc_comment(previous.start_line, true); + } else if (has_comment(doc_comment_line, true)) { if constexpr (std::is_same_v<T, ClassNode>) { get_class_doc_comment(doc_comment_line, member->doc_brief_description, member->doc_description, member->doc_tutorials, true); } else { @@ -799,10 +806,15 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)() void GDScriptParser::parse_class_body(bool p_is_multiline) { bool class_end = false; + bool next_is_static = false; while (!class_end && !is_at_end()) { - switch (current.type) { + GDScriptTokenizer::Token token = current; + switch (token.type) { case GDScriptTokenizer::Token::VAR: - parse_class_member(&GDScriptParser::parse_variable, AnnotationInfo::VARIABLE, "variable"); + parse_class_member(&GDScriptParser::parse_variable, AnnotationInfo::VARIABLE, "variable", next_is_static); + if (next_is_static) { + current_class->has_static_data = true; + } break; case GDScriptTokenizer::Token::CONST: parse_class_member(&GDScriptParser::parse_constant, AnnotationInfo::CONSTANT, "constant"); @@ -810,9 +822,8 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) { case GDScriptTokenizer::Token::SIGNAL: parse_class_member(&GDScriptParser::parse_signal, AnnotationInfo::SIGNAL, "signal"); break; - case GDScriptTokenizer::Token::STATIC: case GDScriptTokenizer::Token::FUNC: - parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function"); + parse_class_member(&GDScriptParser::parse_function, AnnotationInfo::FUNCTION, "function", next_is_static); break; case GDScriptTokenizer::Token::CLASS: parse_class_member(&GDScriptParser::parse_class, AnnotationInfo::CLASS, "class"); @@ -820,6 +831,13 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) { case GDScriptTokenizer::Token::ENUM: parse_class_member(&GDScriptParser::parse_enum, AnnotationInfo::NONE, "enum"); break; + case GDScriptTokenizer::Token::STATIC: { + advance(); + next_is_static = true; + if (!check(GDScriptTokenizer::Token::FUNC) && !check(GDScriptTokenizer::Token::VAR)) { + push_error(R"(Expected "func" or "var" after "static".)"); + } + } break; case GDScriptTokenizer::Token::ANNOTATION: { advance(); @@ -866,6 +884,9 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) { advance(); break; } + if (token.type != GDScriptTokenizer::Token::STATIC) { + next_is_static = false; + } if (panic_mode) { synchronize(); } @@ -875,11 +896,11 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) { } } -GDScriptParser::VariableNode *GDScriptParser::parse_variable() { - return parse_variable(true); +GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static) { + return parse_variable(p_is_static, true); } -GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_property) { +GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_is_static, bool p_allow_property) { VariableNode *variable = alloc_node<VariableNode>(); if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected variable name after "var".)")) { @@ -889,6 +910,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper variable->identifier = parse_identifier(); variable->export_info.name = variable->identifier->name; + variable->is_static = p_is_static; if (match(GDScriptTokenizer::Token::COLON)) { if (check(GDScriptTokenizer::Token::NEWLINE)) { @@ -1032,6 +1054,7 @@ void GDScriptParser::parse_property_setter(VariableNode *p_variable) { complete_extents(identifier); identifier->name = "@" + p_variable->identifier->name + "_setter"; function->identifier = identifier; + function->is_static = p_variable->is_static; consume(GDScriptTokenizer::Token::PARENTHESIS_OPEN, R"(Expected "(" after "set".)"); @@ -1083,6 +1106,7 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) { complete_extents(identifier); identifier->name = "@" + p_variable->identifier->name + "_getter"; function->identifier = identifier; + function->is_static = p_variable->is_static; FunctionNode *previous_function = current_function; current_function = function; @@ -1107,7 +1131,7 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) { } } -GDScriptParser::ConstantNode *GDScriptParser::parse_constant() { +GDScriptParser::ConstantNode *GDScriptParser::parse_constant(bool p_is_static) { ConstantNode *constant = alloc_node<ConstantNode>(); if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected constant name after "const".)")) { @@ -1174,7 +1198,7 @@ GDScriptParser::ParameterNode *GDScriptParser::parse_parameter() { return parameter; } -GDScriptParser::SignalNode *GDScriptParser::parse_signal() { +GDScriptParser::SignalNode *GDScriptParser::parse_signal(bool p_is_static) { SignalNode *signal = alloc_node<SignalNode>(); if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected signal name after "signal".)")) { @@ -1219,7 +1243,7 @@ GDScriptParser::SignalNode *GDScriptParser::parse_signal() { return signal; } -GDScriptParser::EnumNode *GDScriptParser::parse_enum() { +GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) { EnumNode *enum_node = alloc_node<EnumNode>(); bool named = false; @@ -1343,7 +1367,7 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod default_used = true; } else { if (default_used) { - push_error("Cannot have a mandatory parameters after optional parameters."); + push_error("Cannot have mandatory parameters after optional parameters."); continue; } } @@ -1368,23 +1392,23 @@ void GDScriptParser::parse_function_signature(FunctionNode *p_function, SuiteNod } } + if (!p_function->source_lambda && p_function->identifier && p_function->identifier->name == GDScriptLanguage::get_singleton()->strings._static_init) { + if (!p_function->is_static) { + push_error(R"(Static constructor must be declared static.)"); + } + if (p_function->parameters.size() != 0) { + push_error(R"(Static constructor cannot have parameters.)"); + } + current_class->has_static_data = true; + } + // TODO: Improve token consumption so it synchronizes to a statement boundary. This way we can get into the function body with unrecognized tokens. consume(GDScriptTokenizer::Token::COLON, vformat(R"(Expected ":" after %s declaration.)", p_type)); } -GDScriptParser::FunctionNode *GDScriptParser::parse_function() { +GDScriptParser::FunctionNode *GDScriptParser::parse_function(bool p_is_static) { FunctionNode *function = alloc_node<FunctionNode>(); - bool _static = false; - if (previous.type == GDScriptTokenizer::Token::STATIC) { - // TODO: Improve message if user uses "static" with "var" or "const" - if (!consume(GDScriptTokenizer::Token::FUNC, R"(Expected "func" after "static".)")) { - complete_extents(function); - return nullptr; - } - _static = true; - } - make_completion_context(COMPLETION_OVERRIDE_METHOD, function); if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected function name after "func".)")) { @@ -1396,7 +1420,7 @@ GDScriptParser::FunctionNode *GDScriptParser::parse_function() { current_function = function; function->identifier = parse_identifier(); - function->is_static = _static; + function->is_static = p_is_static; SuiteNode *body = alloc_node<SuiteNode>(); SuiteNode *previous_suite = current_suite; @@ -1439,27 +1463,32 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali valid = false; } - if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { + if (check(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { + push_multiline(true); + advance(); // Arguments. push_completion_call(annotation); make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true); - if (!check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE) && !is_at_end()) { - push_multiline(true); - int argument_index = 0; - do { - make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true); - set_last_completion_call_arg(argument_index++); - ExpressionNode *argument = parse_expression(false); - if (argument == nullptr) { - valid = false; - continue; - } - annotation->arguments.push_back(argument); - } while (match(GDScriptTokenizer::Token::COMMA)); - pop_multiline(); + int argument_index = 0; + do { + if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { + // Allow for trailing comma. + break; + } - consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after annotation arguments.)*"); - } + make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true); + set_last_completion_call_arg(argument_index++); + ExpressionNode *argument = parse_expression(false); + if (argument == nullptr) { + push_error("Expected expression as the annotation argument."); + valid = false; + continue; + } + annotation->arguments.push_back(argument); + } while (match(GDScriptTokenizer::Token::COMMA) && !is_at_end()); + + pop_multiline(); + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after annotation arguments.)*"); pop_completion_call(); } complete_extents(annotation); @@ -1475,7 +1504,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali void GDScriptParser::clear_unused_annotations() { for (const AnnotationNode *annotation : annotation_stack) { - push_error(vformat(R"(Annotation "%s" does not precedes a valid target, so it will have no effect.)", annotation->name), annotation); + push_error(vformat(R"(Annotation "%s" does not precede a valid target, so it will have no effect.)", annotation->name), annotation); } annotation_stack.clear(); @@ -1603,11 +1632,11 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { break; case GDScriptTokenizer::Token::VAR: advance(); - result = parse_variable(); + result = parse_variable(false, false); break; case GDScriptTokenizer::Token::CONST: advance(); - result = parse_constant(); + result = parse_constant(false); break; case GDScriptTokenizer::Token::IF: advance(); @@ -1637,7 +1666,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() { advance(); ReturnNode *n_return = alloc_node<ReturnNode>(); if (!is_statement_end()) { - if (current_function && current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init) { + if (current_function && (current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._init || current_function->identifier->name == GDScriptLanguage::get_singleton()->strings._static_init)) { push_error(R"(Constructor cannot return a value.)"); } n_return->return_value = parse_expression(false); @@ -1817,7 +1846,7 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { n_for->list = parse_expression(false); if (!n_for->list) { - push_error(R"(Expected a list or range after "in".)"); + push_error(R"(Expected iterable after "in".)"); } consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "for" condition.)"); @@ -2820,6 +2849,9 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode * attribute->base = p_previous_operand; + if (current.is_node_name()) { + current.type = GDScriptTokenizer::Token::IDENTIFIER; + } if (!consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier after "." for attribute access.)")) { complete_extents(attribute); return attribute; @@ -3261,35 +3293,125 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { } #ifdef TOOLS_ENABLED -static bool _in_codeblock(String p_line, bool p_already_in, int *r_block_begins = nullptr) { - int start_block = p_line.rfind("[codeblock]"); - int end_block = p_line.rfind("[/codeblock]"); - - if (start_block != -1 && r_block_begins) { - *r_block_begins = start_block; +enum DocLineState { + DOC_LINE_NORMAL, + DOC_LINE_IN_CODE, + DOC_LINE_IN_CODEBLOCK, +}; + +static String _process_doc_line(const String &p_line, const String &p_text, const String &p_space_prefix, DocLineState &r_state) { + String line = p_line; + if (r_state == DOC_LINE_NORMAL) { + line = line.strip_edges(true, false); + } else { + line = line.trim_prefix(p_space_prefix); } - if (p_already_in) { - if (end_block == -1) { - return true; - } else if (start_block == -1) { - return false; + String line_join; + if (!p_text.is_empty()) { + if (r_state == DOC_LINE_NORMAL) { + if (p_text.ends_with("[/codeblock]")) { + line_join = "\n"; + } else if (!p_text.ends_with("[br]")) { + line_join = " "; + } } else { - return start_block > end_block; + line_join = "\n"; } - } else { - if (start_block == -1) { - return false; - } else if (end_block == -1) { - return true; - } else { - return start_block > end_block; + } + + String result; + int from = 0; + int buffer_start = 0; + const int len = line.length(); + bool process = true; + while (process) { + switch (r_state) { + case DOC_LINE_NORMAL: { + int lb_pos = line.find_char('[', from); + if (lb_pos < 0) { + process = false; + break; + } + int rb_pos = line.find_char(']', lb_pos + 1); + if (rb_pos < 0) { + process = false; + break; + } + + from = rb_pos + 1; + + String tag = line.substr(lb_pos + 1, rb_pos - lb_pos - 1); + if (tag == "code") { + r_state = DOC_LINE_IN_CODE; + } else if (tag == "codeblock") { + if (lb_pos == 0) { + line_join = "\n"; + } else { + result += line.substr(buffer_start, lb_pos - buffer_start) + '\n'; + } + result += "[codeblock]"; + if (from < len) { + result += '\n'; + } + + r_state = DOC_LINE_IN_CODEBLOCK; + buffer_start = from; + } + } break; + case DOC_LINE_IN_CODE: { + int pos = line.find("[/code]", from); + if (pos < 0) { + process = false; + break; + } + + from = pos + 7; + + r_state = DOC_LINE_NORMAL; + } break; + case DOC_LINE_IN_CODEBLOCK: { + int pos = line.find("[/codeblock]", from); + if (pos < 0) { + process = false; + break; + } + + from = pos + 12; + + if (pos == 0) { + line_join = "\n"; + } else { + result += line.substr(buffer_start, pos - buffer_start) + '\n'; + } + result += "[/codeblock]"; + if (from < len) { + result += '\n'; + } + + r_state = DOC_LINE_NORMAL; + buffer_start = from; + } break; } } + + result += line.substr(buffer_start); + if (r_state == DOC_LINE_NORMAL) { + result = result.strip_edges(false, true); + } + + return line_join + result; } -bool GDScriptParser::has_comment(int p_line) { - return tokenizer.get_comments().has(p_line); +bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) { + bool has_comment = tokenizer.get_comments().has(p_line); + // If there are no comments or if we don't care whether the comment + // is a docstring, we have our result. + if (!p_must_be_doc || !has_comment) { + return has_comment; + } + + return tokenizer.get_comments()[p_line].comment.begins_with("##"); } String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) { @@ -3306,7 +3428,7 @@ String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) { String doc; int line = p_line; - bool in_codeblock = false; + DocLineState state = DOC_LINE_NORMAL; while (comments.has(line - 1)) { if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) { @@ -3315,29 +3437,24 @@ String GDScriptParser::get_doc_comment(int p_line, bool p_single_line) { line--; } - int codeblock_begins = 0; + String space_prefix; + if (comments.has(line) && comments[line].comment.begins_with("##")) { + int i = 2; + for (; i < comments[line].comment.length(); i++) { + if (comments[line].comment[i] != ' ') { + break; + } + } + space_prefix = String(" ").repeat(i - 2); + } + while (comments.has(line)) { if (!comments[line].new_line || !comments[line].comment.begins_with("##")) { break; } - String doc_line = comments[line].comment.trim_prefix("##"); - - in_codeblock = _in_codeblock(doc_line, in_codeblock, &codeblock_begins); - if (in_codeblock) { - int i = 0; - for (; i < codeblock_begins; i++) { - if (doc_line[i] != ' ') { - break; - } - } - doc_line = doc_line.substr(i); - } else { - doc_line = doc_line.strip_edges(); - } - String line_join = (in_codeblock) ? "\n" : " "; - - doc = (doc.is_empty()) ? doc_line : doc + line_join + doc_line; + String doc_line = comments[line].comment.trim_prefix("##"); + doc += _process_doc_line(doc_line, doc, space_prefix, state); line++; } @@ -3352,7 +3469,7 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & ERR_FAIL_COND(!p_brief.is_empty() || !p_desc.is_empty() || p_tutorials.size() != 0); int line = p_line; - bool in_codeblock = false; + DocLineState state = DOC_LINE_NORMAL; enum Mode { BRIEF, DESC, @@ -3370,96 +3487,87 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & } } - int codeblock_begins = 0; + String space_prefix; + if (comments.has(line) && comments[line].comment.begins_with("##")) { + int i = 2; + for (; i < comments[line].comment.length(); i++) { + if (comments[line].comment[i] != ' ') { + break; + } + } + space_prefix = String(" ").repeat(i - 2); + } + while (comments.has(line)) { if (!comments[line].new_line || !comments[line].comment.begins_with("##")) { break; } - String title, link; // For tutorials. String doc_line = comments[line++].comment.trim_prefix("##"); - String stripped_line = doc_line.strip_edges(); - - // Set the read mode. - if (stripped_line.is_empty() && mode == BRIEF && !p_brief.is_empty()) { - mode = DESC; - continue; - - } else if (stripped_line.begins_with("@tutorial")) { - int begin_scan = String("@tutorial").length(); - if (begin_scan >= stripped_line.length()) { - continue; // invalid syntax. - } - - if (stripped_line[begin_scan] == ':') { // No title. - // Syntax: ## @tutorial: https://godotengine.org/ // The title argument is optional. - title = ""; - link = stripped_line.trim_prefix("@tutorial:").strip_edges(); - - } else { - /* Syntax: - * @tutorial ( The Title Here ) : https://the.url/ - * ^ open ^ close ^ colon ^ url - */ - int open_bracket_pos = begin_scan, close_bracket_pos = 0; - while (open_bracket_pos < stripped_line.length() && (stripped_line[open_bracket_pos] == ' ' || stripped_line[open_bracket_pos] == '\t')) { - open_bracket_pos++; - } - if (open_bracket_pos == stripped_line.length() || stripped_line[open_bracket_pos++] != '(') { - continue; // invalid syntax. - } - close_bracket_pos = open_bracket_pos; - while (close_bracket_pos < stripped_line.length() && stripped_line[close_bracket_pos] != ')') { - close_bracket_pos++; - } - if (close_bracket_pos == stripped_line.length()) { - continue; // invalid syntax. - } + String title, link; // For tutorials. - int colon_pos = close_bracket_pos + 1; - while (colon_pos < stripped_line.length() && (stripped_line[colon_pos] == ' ' || stripped_line[colon_pos] == '\t')) { - colon_pos++; + if (state == DOC_LINE_NORMAL) { + // Set the read mode. + String stripped_line = doc_line.strip_edges(); + if (stripped_line.is_empty()) { + if (mode == BRIEF && !p_brief.is_empty()) { + mode = DESC; } - if (colon_pos == stripped_line.length() || stripped_line[colon_pos++] != ':') { - continue; // invalid syntax. + continue; + } else if (stripped_line.begins_with("@tutorial")) { + int begin_scan = String("@tutorial").length(); + if (begin_scan >= stripped_line.length()) { + continue; // Invalid syntax. } - title = stripped_line.substr(open_bracket_pos, close_bracket_pos - open_bracket_pos).strip_edges(); - link = stripped_line.substr(colon_pos).strip_edges(); - } - - mode = TUTORIALS; - in_codeblock = false; - } else if (stripped_line.is_empty()) { - continue; - } else { - // Tutorial docs are single line, we need a @tag after it. - if (mode == TUTORIALS) { - mode = DONE; - } + if (stripped_line[begin_scan] == ':') { // No title. + // Syntax: ## @tutorial: https://godotengine.org/ // The title argument is optional. + title = ""; + link = stripped_line.trim_prefix("@tutorial:").strip_edges(); + } else { + /* Syntax: + * @tutorial ( The Title Here ) : https://the.url/ + * ^ open ^ close ^ colon ^ url + */ + int open_bracket_pos = begin_scan, close_bracket_pos = 0; + while (open_bracket_pos < stripped_line.length() && (stripped_line[open_bracket_pos] == ' ' || stripped_line[open_bracket_pos] == '\t')) { + open_bracket_pos++; + } + if (open_bracket_pos == stripped_line.length() || stripped_line[open_bracket_pos++] != '(') { + continue; // Invalid syntax. + } + close_bracket_pos = open_bracket_pos; + while (close_bracket_pos < stripped_line.length() && stripped_line[close_bracket_pos] != ')') { + close_bracket_pos++; + } + if (close_bracket_pos == stripped_line.length()) { + continue; // Invalid syntax. + } - in_codeblock = _in_codeblock(doc_line, in_codeblock, &codeblock_begins); - } + int colon_pos = close_bracket_pos + 1; + while (colon_pos < stripped_line.length() && (stripped_line[colon_pos] == ' ' || stripped_line[colon_pos] == '\t')) { + colon_pos++; + } + if (colon_pos == stripped_line.length() || stripped_line[colon_pos++] != ':') { + continue; // Invalid syntax. + } - if (in_codeblock) { - int i = 0; - for (; i < codeblock_begins; i++) { - if (doc_line[i] != ' ') { - break; + title = stripped_line.substr(open_bracket_pos, close_bracket_pos - open_bracket_pos).strip_edges(); + link = stripped_line.substr(colon_pos).strip_edges(); } + + mode = TUTORIALS; + } else if (mode == TUTORIALS) { // Tutorial docs are single line, we need a @tag after it. + mode = DONE; } - doc_line = doc_line.substr(i); - } else { - doc_line = stripped_line; } - String line_join = (in_codeblock) ? "\n" : " "; switch (mode) { case BRIEF: - p_brief = (p_brief.length() == 0) ? doc_line : p_brief + line_join + doc_line; + p_brief += _process_doc_line(doc_line, p_brief, space_prefix, state); break; case DESC: - p_desc = (p_desc.length() == 0) ? doc_line : p_desc + line_join + doc_line; + p_desc += _process_doc_line(doc_line, p_desc, space_prefix, state); break; case TUTORIALS: p_tutorials.append(Pair<String, String>(title, link)); @@ -3468,6 +3576,7 @@ void GDScriptParser::get_class_doc_comment(int p_line, String &p_brief, String & break; } } + if (current_class->members.size() > 0) { const ClassNode::Member &m = current_class->members[0]; int first_member_line = m.get_line(); @@ -3678,6 +3787,12 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) } bool GDScriptParser::tool_annotation(const AnnotationNode *p_annotation, Node *p_node) { +#ifdef DEBUG_ENABLED + if (this->_is_tool) { + push_error(R"("@tool" annotation can only be used once.)", p_annotation); + return false; + } +#endif // DEBUG_ENABLED this->_is_tool = true; return true; } @@ -3686,6 +3801,16 @@ bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p ERR_FAIL_COND_V_MSG(p_node->type != Node::CLASS, false, R"("@icon" annotation can only be applied to classes.)"); ERR_FAIL_COND_V(p_annotation->resolved_arguments.is_empty(), false); ClassNode *p_class = static_cast<ClassNode *>(p_node); +#ifdef DEBUG_ENABLED + if (!p_class->icon_path.is_empty()) { + push_error(R"("@icon" annotation can only be used once.)", p_annotation); + return false; + } + if (String(p_annotation->resolved_arguments[0]).is_empty()) { + push_error(R"("@icon" annotation argument must contain the path to the icon.)", p_annotation->arguments[0]); + return false; + } +#endif // DEBUG_ENABLED p_class->icon_path = p_annotation->resolved_arguments[0]; return true; } @@ -3856,7 +3981,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node 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); + push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable); return false; } @@ -3901,8 +4026,7 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node variable->export_info.hint_string = enum_hint_string; } break; default: - // TODO: Allow custom user resources. - push_error(R"(Export type can only be built-in, a resource, or an enum.)", variable); + push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable); break; } @@ -4067,6 +4191,17 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_ return true; } +bool GDScriptParser::static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target) { + ERR_FAIL_COND_V_MSG(p_target->type != Node::CLASS, false, vformat(R"("%s" annotation can only be applied to classes.)", p_annotation->name)); + ClassNode *p_class = static_cast<ClassNode *>(p_target); + if (p_class->annotated_static_unload) { + push_error(vformat(R"("%s" annotation can only be used once per script.)", p_annotation->name), p_annotation); + return false; + } + p_class->annotated_static_unload = true; + return true; +} + GDScriptParser::DataType GDScriptParser::SuiteNode::Local::get_datatype() const { switch (type) { case CONSTANT: @@ -4121,16 +4256,13 @@ String GDScriptParser::DataType::to_string() const { } return native_type.operator String(); case CLASS: - if (is_meta_type) { - return GDScript::get_class_static(); - } if (class_type->identifier != nullptr) { return class_type->identifier->name.operator String(); } return class_type->fqcn; case SCRIPT: { if (is_meta_type) { - return script_type->get_class_name().operator String(); + return script_type != nullptr ? script_type->get_class_name().operator String() : ""; } String name = script_type != nullptr ? script_type->get_name() : ""; if (!name.is_empty()) { diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 29841ab060..8f0265510f 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -709,6 +709,8 @@ public: ClassNode *outer = nullptr; bool extends_used = false; bool onready_used = false; + bool has_static_data = false; + bool annotated_static_unload = false; String extends_path; Vector<IdentifierNode *> extends; // List for indexing: extends A.B.C DataType base_type; @@ -847,6 +849,7 @@ public: LOCAL_BIND, // Pattern bind. MEMBER_SIGNAL, MEMBER_VARIABLE, + STATIC_VARIABLE, MEMBER_CONSTANT, INHERITED_VARIABLE, }; @@ -1202,6 +1205,7 @@ public: bool onready = false; PropertyInfo export_info; int assignments = 0; + bool is_static = false; #ifdef TOOLS_ENABLED String doc_description; #endif // TOOLS_ENABLED @@ -1405,16 +1409,16 @@ private: // Main blocks. void parse_program(); - ClassNode *parse_class(); + ClassNode *parse_class(bool p_is_static); void parse_class_name(); void parse_extends(); void parse_class_body(bool p_is_multiline); template <class T> - void parse_class_member(T *(GDScriptParser::*p_parse_function)(), AnnotationInfo::TargetKind p_target, const String &p_member_kind); - SignalNode *parse_signal(); - EnumNode *parse_enum(); + void parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static = false); + SignalNode *parse_signal(bool p_is_static); + EnumNode *parse_enum(bool p_is_static); ParameterNode *parse_parameter(); - FunctionNode *parse_function(); + FunctionNode *parse_function(bool p_is_static); void parse_function_signature(FunctionNode *p_function, SuiteNode *p_body, const String &p_type); SuiteNode *parse_suite(const String &p_context, SuiteNode *p_suite = nullptr, bool p_for_lambda = false); // Annotations @@ -1431,14 +1435,15 @@ private: bool export_group_annotations(const AnnotationNode *p_annotation, Node *p_target); bool warning_annotations(const AnnotationNode *p_annotation, Node *p_target); bool rpc_annotation(const AnnotationNode *p_annotation, Node *p_target); + bool static_unload_annotation(const AnnotationNode *p_annotation, Node *p_target); // Statements. Node *parse_statement(); - VariableNode *parse_variable(); - VariableNode *parse_variable(bool p_allow_property); + VariableNode *parse_variable(bool p_is_static); + VariableNode *parse_variable(bool p_is_static, bool p_allow_property); VariableNode *parse_property(VariableNode *p_variable, bool p_need_indent); void parse_property_getter(VariableNode *p_variable); void parse_property_setter(VariableNode *p_variable); - ConstantNode *parse_constant(); + ConstantNode *parse_constant(bool p_is_static); AssertNode *parse_assert(); BreakNode *parse_break(); ContinueNode *parse_continue(); @@ -1480,7 +1485,7 @@ private: #ifdef TOOLS_ENABLED // Doc comments. int class_doc_line = 0x7FFFFFFF; - bool has_comment(int p_line); + bool has_comment(int p_line, bool p_must_be_doc = false); String get_doc_comment(int p_line, bool p_single_line = false); void get_class_doc_comment(int p_line, String &p_brief, String &p_desc, Vector<Pair<String, String>> &p_tutorials, bool p_inner_class); #endif // TOOLS_ENABLED diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index d586380c41..a45a73a8d5 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -1099,7 +1099,7 @@ void GDScriptTokenizer::check_indent() { _advance(); } - if (mixed) { + if (mixed && !(line_continuation || multiline_mode)) { Token error = make_error("Mixed use of tabs and spaces for indentation."); error.start_line = line; error.start_column = 1; diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 83d2ed6010..bebf34cbb3 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -680,10 +680,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a 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 }; + int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0, script->static_variables.size() }; #endif - Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr }; + Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr, script->static_variables.ptrw() }; #ifdef DEBUG_ENABLED OPCODE_WHILE(ip < _code_size) { @@ -774,7 +774,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a bool result = false; if (value->get_type() == Variant::ARRAY) { Array *array = VariantInternal::get_array(value); - result = array->get_typed_builtin() == ((uint32_t)builtin_type) && array->get_typed_class_name() == native_type && array->get_typed_script() == *script_type && array->get_typed_class_name() == native_type; + result = array->get_typed_builtin() == ((uint32_t)builtin_type) && array->get_typed_class_name() == native_type && array->get_typed_script() == *script_type; } *dst = result; @@ -1252,7 +1252,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a Array *array = VariantInternal::get_array(src); - if (array->get_typed_builtin() != ((uint32_t)builtin_type) || array->get_typed_class_name() != native_type || array->get_typed_script() != *script_type || array->get_typed_class_name() != native_type) { + if (array->get_typed_builtin() != ((uint32_t)builtin_type) || array->get_typed_class_name() != native_type || array->get_typed_script() != *script_type) { #ifdef DEBUG_ENABLED err_text = vformat(R"(Trying to assign an array of type "%s" to a variable of type "Array[%s]".)", _get_var_type(src), _get_element_type(builtin_type, native_type, *script_type)); @@ -1651,10 +1651,6 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a bool was_freed = false; Object *obj = ret->get_validated_object_with_check(was_freed); - if (was_freed) { - err_text = "Got a freed object as a result of the call."; - OPCODE_BREAK; - } if (obj && obj->is_class_ptr(GDScriptFunctionState::get_class_ptr_static())) { err_text = R"(Trying to call an async function without "await".)"; OPCODE_BREAK; @@ -2583,7 +2579,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a Array *array = VariantInternal::get_array(r); - if (array->get_typed_builtin() != ((uint32_t)builtin_type) || array->get_typed_class_name() != native_type || array->get_typed_script() != *script_type || array->get_typed_class_name() != native_type) { + if (array->get_typed_builtin() != ((uint32_t)builtin_type) || array->get_typed_class_name() != native_type || array->get_typed_script() != *script_type) { #ifdef DEBUG_ENABLED err_text = vformat(R"(Trying to return an array of type "%s" where expected return type is "Array[%s]".)", _get_var_type(r), _get_element_type(builtin_type, native_type, *script_type)); @@ -3572,8 +3568,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a //error // function, file, line, error, explanation String err_file; - if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && !p_instance->script->path.is_empty()) { - err_file = p_instance->script->path; + bool instance_valid_with_script = p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid(); + if (instance_valid_with_script && !get_script()->path.is_empty()) { + err_file = get_script()->path; } else if (script) { err_file = script->path; } @@ -3581,7 +3578,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a err_file = "<built-in>"; } String err_func = name; - if (p_instance && ObjectDB::get_instance(p_instance->owner_id) != nullptr && p_instance->script->is_valid() && !p_instance->script->name.is_empty()) { + if (instance_valid_with_script && !p_instance->script->name.is_empty()) { err_func = p_instance->script->name + "." + err_func; } int err_line = line; diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index ef59a07f1a..8de78d2b9a 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -38,153 +38,115 @@ String GDScriptWarning::get_message() const { #define CHECK_SYMBOLS(m_amount) ERR_FAIL_COND_V(symbols.size() < m_amount, String()); switch (code) { - case UNASSIGNED_VARIABLE_OP_ASSIGN: { + case UNASSIGNED_VARIABLE: CHECK_SYMBOLS(1); - return "Using assignment with operation but the variable '" + symbols[0] + "' was not previously assigned a value."; - } break; - case UNASSIGNED_VARIABLE: { + return vformat(R"(The variable "%s" was used but never assigned a value.)", symbols[0]); + case UNASSIGNED_VARIABLE_OP_ASSIGN: CHECK_SYMBOLS(1); - return "The variable '" + symbols[0] + "' was used but never assigned a value."; - } break; - case UNUSED_VARIABLE: { + return vformat(R"(Using assignment with operation but the variable "%s" was not previously assigned a value.)", symbols[0]); + case UNUSED_VARIABLE: CHECK_SYMBOLS(1); - return "The local variable '" + symbols[0] + "' is declared but never used in the block. If this is intended, prefix it with an underscore: '_" + symbols[0] + "'"; - } break; - case UNUSED_LOCAL_CONSTANT: { + return vformat(R"(The local variable "%s" is declared but never used in the block. If this is intended, prefix it with an underscore: "_%s".)", symbols[0], symbols[0]); + case UNUSED_LOCAL_CONSTANT: CHECK_SYMBOLS(1); - return "The local constant '" + symbols[0] + "' is declared but never used in the block. If this is intended, prefix it with an underscore: '_" + symbols[0] + "'"; - } break; - case SHADOWED_VARIABLE: { + return vformat(R"(The local constant "%s" is declared but never used in the block. If this is intended, prefix it with an underscore: "_%s".)", symbols[0], symbols[0]); + case UNUSED_PRIVATE_CLASS_VARIABLE: + CHECK_SYMBOLS(1); + return vformat(R"(The class variable "%s" is declared but never used in the script.)", symbols[0]); + case UNUSED_PARAMETER: + CHECK_SYMBOLS(2); + return vformat(R"*(The parameter "%s" is never used in the function "%s()". If this is intended, prefix it with an underscore: "_%s".)*", symbols[1], symbols[0], symbols[1]); + case UNUSED_SIGNAL: + CHECK_SYMBOLS(1); + return vformat(R"(The signal "%s" is declared but never emitted.)", symbols[0]); + case SHADOWED_VARIABLE: CHECK_SYMBOLS(4); return vformat(R"(The local %s "%s" is shadowing an already-declared %s at line %s.)", symbols[0], symbols[1], symbols[2], symbols[3]); - } break; - case SHADOWED_VARIABLE_BASE_CLASS: { + case SHADOWED_VARIABLE_BASE_CLASS: CHECK_SYMBOLS(4); return vformat(R"(The local %s "%s" is shadowing an already-declared %s at the base class "%s".)", symbols[0], symbols[1], symbols[2], symbols[3]); - } break; - case UNUSED_PRIVATE_CLASS_VARIABLE: { - CHECK_SYMBOLS(1); - return "The class variable '" + symbols[0] + "' is declared but never used in the script."; - } break; - case UNUSED_PARAMETER: { - CHECK_SYMBOLS(2); - return "The parameter '" + symbols[1] + "' is never used in the function '" + symbols[0] + "'. If this is intended, prefix it with an underscore: '_" + symbols[1] + "'"; - } break; - case UNREACHABLE_CODE: { + case SHADOWED_GLOBAL_IDENTIFIER: + CHECK_SYMBOLS(3); + return vformat(R"(The %s "%s" has the same name as a %s.)", symbols[0], symbols[1], symbols[2]); + case UNREACHABLE_CODE: CHECK_SYMBOLS(1); - return "Unreachable code (statement after return) in function '" + symbols[0] + "()'."; - } break; - case UNREACHABLE_PATTERN: { + return vformat(R"*(Unreachable code (statement after return) in function "%s()".)*", symbols[0]); + case UNREACHABLE_PATTERN: return "Unreachable pattern (pattern after wildcard or bind)."; - } break; - case STANDALONE_EXPRESSION: { + case STANDALONE_EXPRESSION: return "Standalone expression (the line has no effect)."; - } break; - case NARROWING_CONVERSION: { - return "Narrowing conversion (float is converted to int and loses precision)."; - } break; - case INCOMPATIBLE_TERNARY: { + case STANDALONE_TERNARY: + return "Standalone ternary conditional operator: the return value is being discarded."; + case INCOMPATIBLE_TERNARY: return "Values of the ternary conditional are not mutually compatible."; - } break; - case UNUSED_SIGNAL: { - CHECK_SYMBOLS(1); - return "The signal '" + symbols[0] + "' is declared but never emitted."; - } break; - case RETURN_VALUE_DISCARDED: { - CHECK_SYMBOLS(1); - return "The function '" + symbols[0] + "()' returns a value that will be discarded if not used."; - } break; - case PROPERTY_USED_AS_FUNCTION: { + case PROPERTY_USED_AS_FUNCTION: CHECK_SYMBOLS(2); - return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a property with the same name. Did you mean to access it?"; - } break; - case CONSTANT_USED_AS_FUNCTION: { + return vformat(R"*(The method "%s()" was not found in base "%s" but there's a property with the same name. Did you mean to access it?)*", symbols[0], symbols[1]); + case CONSTANT_USED_AS_FUNCTION: CHECK_SYMBOLS(2); - return "The method '" + symbols[0] + "()' was not found in base '" + symbols[1] + "' but there's a constant with the same name. Did you mean to access it?"; - } break; - case FUNCTION_USED_AS_PROPERTY: { + return vformat(R"*(The method "%s()" was not found in base "%s" but there's a constant with the same name. Did you mean to access it?)*", symbols[0], symbols[1]); + case FUNCTION_USED_AS_PROPERTY: CHECK_SYMBOLS(2); - return "The property '" + symbols[0] + "' was not found in base '" + symbols[1] + "' but there's a method with the same name. Did you mean to call it?"; - } break; - case INTEGER_DIVISION: { - return "Integer division, decimal part will be discarded."; - } break; - case UNSAFE_PROPERTY_ACCESS: { + return vformat(R"(The property "%s" was not found in base "%s" but there's a method with the same name. Did you mean to call it?)", symbols[0], symbols[1]); + case UNSAFE_PROPERTY_ACCESS: CHECK_SYMBOLS(2); - return "The property '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype)."; - } break; - case UNSAFE_METHOD_ACCESS: { + return vformat(R"(The property "%s" is not present on the inferred type "%s" (but may be present on a subtype).)", symbols[0], symbols[1]); + case UNSAFE_METHOD_ACCESS: CHECK_SYMBOLS(2); - return "The method '" + symbols[0] + "' is not present on the inferred type '" + symbols[1] + "' (but may be present on a subtype)."; - } break; - case UNSAFE_CAST: { + return vformat(R"*(The method "%s()" is not present on the inferred type "%s" (but may be present on a subtype).)*", symbols[0], symbols[1]); + case UNSAFE_CAST: CHECK_SYMBOLS(1); - return "The value is cast to '" + symbols[0] + "' but has an unknown type."; - } break; - case UNSAFE_CALL_ARGUMENT: { + return vformat(R"(The value is cast to "%s" but has an unknown type.)", symbols[0]); + case UNSAFE_CALL_ARGUMENT: CHECK_SYMBOLS(4); - return "The argument '" + symbols[0] + "' of the function '" + symbols[1] + "' requires a the subtype '" + symbols[2] + "' but the supertype '" + symbols[3] + "' was provided"; - } break; - case UNSAFE_VOID_RETURN: { + return vformat(R"*(The argument %s of the function "%s()" requires a the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3]); + case UNSAFE_VOID_RETURN: CHECK_SYMBOLS(2); - return "The method '" + symbols[0] + "()' returns 'void' but it's trying to return a call to '" + symbols[1] + "()' that can't be ensured to also be 'void'."; - } break; - case DEPRECATED_KEYWORD: { + return vformat(R"*(The method "%s()" returns "void" but it's trying to return a call to "%s()" that can't be ensured to also be "void".)*", symbols[0], symbols[1]); + case RETURN_VALUE_DISCARDED: + CHECK_SYMBOLS(1); + return vformat(R"*(The function "%s()" returns a value that will be discarded if not used.)*", symbols[0]); + case STATIC_CALLED_ON_INSTANCE: CHECK_SYMBOLS(2); - return "The '" + symbols[0] + "' keyword is deprecated and will be removed in a future release, please replace its uses by '" + symbols[1] + "'."; - } break; - case STANDALONE_TERNARY: { - return "Standalone ternary conditional operator: the return value is being discarded."; - } - case ASSERT_ALWAYS_TRUE: { + 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 REDUNDANT_STATIC_UNLOAD: + return R"(The "@static_unload" annotation is redundant because the file does not have a class with static variables.)"; + case REDUNDANT_AWAIT: + return R"("await" keyword not needed in this case, because the expression isn't a coroutine nor a signal.)"; + case ASSERT_ALWAYS_TRUE: return "Assert statement is redundant because the expression is always true."; - } - case ASSERT_ALWAYS_FALSE: { + case ASSERT_ALWAYS_FALSE: return "Assert statement will raise an error because the expression is always false."; - } - case REDUNDANT_AWAIT: { - return R"("await" keyword not needed in this case, because the expression isn't a coroutine nor a signal.)"; - } - case EMPTY_FILE: { - return "Empty script file."; - } - case SHADOWED_GLOBAL_IDENTIFIER: { - CHECK_SYMBOLS(3); - return vformat(R"(The %s '%s' has the same name as a %s.)", symbols[0], symbols[1], symbols[2]); - } - case INT_AS_ENUM_WITHOUT_CAST: { + case INTEGER_DIVISION: + return "Integer division, decimal part will be discarded."; + case NARROWING_CONVERSION: + return "Narrowing conversion (float is converted to int and loses precision)."; + case INT_AS_ENUM_WITHOUT_CAST: return "Integer used when an enum value is expected. If this is intended cast the integer to the enum type."; - } - case INT_AS_ENUM_WITHOUT_MATCH: { + case INT_AS_ENUM_WITHOUT_MATCH: CHECK_SYMBOLS(3); return vformat(R"(Cannot %s %s as Enum "%s": no enum member has matching value.)", symbols[0], symbols[1], symbols[2]); - } break; - case STATIC_CALLED_ON_INSTANCE: { + case EMPTY_FILE: + return "Empty script file."; + case DEPRECATED_KEYWORD: 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: { + return vformat(R"(The "%s" keyword is deprecated and will be removed in a future release, please replace its uses by "%s".)", symbols[0], symbols[1]); + case RENAMED_IN_GODOT_4_HINT: + break; // Renamed identifier hint is taken care of by the GDScriptAnalyzer. No message needed here. + 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 RENAMED_IN_GD4_HINT: { - break; // Renamed identifier hint is taken care of by the GDScriptAnalyzer. No message needed here. - } - case INFERENCE_ON_VARIANT: { + case INFERENCE_ON_VARIANT: CHECK_SYMBOLS(1); return vformat("The %s type is being inferred from a Variant value, so it will be typed as Variant.", symbols[0]); - } - case NATIVE_METHOD_OVERRIDE: { + case NATIVE_METHOD_OVERRIDE: CHECK_SYMBOLS(2); - return vformat(R"(The method "%s" overrides a method from native class "%s". This won't be called by the engine and may not work as expected.)", symbols[0], symbols[1]); - } - case GET_NODE_DEFAULT_WITHOUT_ONREADY: { + return vformat(R"*(The method "%s()" overrides a method from native class "%s". This won't be called by the engine and may not work as expected.)*", symbols[0], symbols[1]); + case GET_NODE_DEFAULT_WITHOUT_ONREADY: CHECK_SYMBOLS(1); return vformat(R"*(The default value is using "%s" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this.)*", symbols[0]); - } - case ONREADY_WITH_EXPORT: { - return R"(The "@onready" annotation will make the default value to be set after the "@export" takes effect and will override it.)"; - } + case ONREADY_WITH_EXPORT: + return R"("@onready" will set the default value after "@export" takes effect and will override it.)"; case WARNING_MAX: break; // Can't happen, but silences warning } @@ -200,7 +162,7 @@ int GDScriptWarning::get_default_value(Code p_code) { PropertyInfo GDScriptWarning::get_property_info(Code p_code) { // Making this a separate function in case a warning needs different PropertyInfo in the future. - if (p_code == Code::RENAMED_IN_GD4_HINT) { + if (p_code == Code::RENAMED_IN_GODOT_4_HINT) { return PropertyInfo(Variant::BOOL, get_settings_path_from_code(p_code)); } return PropertyInfo(Variant::INT, get_settings_path_from_code(p_code), PROPERTY_HINT_ENUM, "Ignore,Warn,Error"); @@ -218,38 +180,39 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "UNASSIGNED_VARIABLE_OP_ASSIGN", "UNUSED_VARIABLE", "UNUSED_LOCAL_CONSTANT", - "SHADOWED_VARIABLE", - "SHADOWED_VARIABLE_BASE_CLASS", "UNUSED_PRIVATE_CLASS_VARIABLE", "UNUSED_PARAMETER", + "UNUSED_SIGNAL", + "SHADOWED_VARIABLE", + "SHADOWED_VARIABLE_BASE_CLASS", + "SHADOWED_GLOBAL_IDENTIFIER", "UNREACHABLE_CODE", "UNREACHABLE_PATTERN", "STANDALONE_EXPRESSION", - "NARROWING_CONVERSION", + "STANDALONE_TERNARY", "INCOMPATIBLE_TERNARY", - "UNUSED_SIGNAL", - "RETURN_VALUE_DISCARDED", "PROPERTY_USED_AS_FUNCTION", "CONSTANT_USED_AS_FUNCTION", "FUNCTION_USED_AS_PROPERTY", - "INTEGER_DIVISION", "UNSAFE_PROPERTY_ACCESS", "UNSAFE_METHOD_ACCESS", "UNSAFE_CAST", "UNSAFE_CALL_ARGUMENT", "UNSAFE_VOID_RETURN", - "DEPRECATED_KEYWORD", - "STANDALONE_TERNARY", + "RETURN_VALUE_DISCARDED", + "STATIC_CALLED_ON_INSTANCE", + "REDUNDANT_STATIC_UNLOAD", + "REDUNDANT_AWAIT", "ASSERT_ALWAYS_TRUE", "ASSERT_ALWAYS_FALSE", - "REDUNDANT_AWAIT", - "EMPTY_FILE", - "SHADOWED_GLOBAL_IDENTIFIER", + "INTEGER_DIVISION", + "NARROWING_CONVERSION", "INT_AS_ENUM_WITHOUT_CAST", "INT_AS_ENUM_WITHOUT_MATCH", - "STATIC_CALLED_ON_INSTANCE", - "CONFUSABLE_IDENTIFIER", + "EMPTY_FILE", + "DEPRECATED_KEYWORD", "RENAMED_IN_GODOT_4_HINT", + "CONFUSABLE_IDENTIFIER", "INFERENCE_ON_VARIANT", "NATIVE_METHOD_OVERRIDE", "GET_NODE_DEFAULT_WITHOUT_ONREADY", diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index f0123c518c..ae6207fcdc 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -50,38 +50,39 @@ public: UNASSIGNED_VARIABLE_OP_ASSIGN, // Variable never assigned but used in an assignment operation (+=, *=, etc). UNUSED_VARIABLE, // Local variable is declared but never used. UNUSED_LOCAL_CONSTANT, // Local constant is declared but never used. - SHADOWED_VARIABLE, // Variable name shadowed by other variable in same class. - SHADOWED_VARIABLE_BASE_CLASS, // Variable name shadowed by other variable in some base class. UNUSED_PRIVATE_CLASS_VARIABLE, // Class variable is declared private ("_" prefix) but never used in the file. UNUSED_PARAMETER, // Function parameter is never used. + UNUSED_SIGNAL, // Signal is defined but never emitted. + SHADOWED_VARIABLE, // Variable name shadowed by other variable in same class. + SHADOWED_VARIABLE_BASE_CLASS, // Variable name shadowed by other variable in some base class. + SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable. 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. - NARROWING_CONVERSION, // Float value into an integer slot, precision is lost. + STANDALONE_TERNARY, // Return value of ternary expression is discarded. INCOMPATIBLE_TERNARY, // Possible values of a ternary if are not mutually compatible. - UNUSED_SIGNAL, // Signal is defined but never emitted. - RETURN_VALUE_DISCARDED, // Function call returns something but the value isn't used. PROPERTY_USED_AS_FUNCTION, // Function not found, but there's a property with the same name. CONSTANT_USED_AS_FUNCTION, // Function not found, but there's a constant with the same name. FUNCTION_USED_AS_PROPERTY, // Property not found, but there's a function with the same name. - INTEGER_DIVISION, // Integer divide by integer, decimal part is discarded. UNSAFE_PROPERTY_ACCESS, // Property not found in the detected type (but can be in subtypes). UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes). UNSAFE_CAST, // Cast used in an unknown type. - UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the require argument. + UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the required type. UNSAFE_VOID_RETURN, // Function returns void but returned a call to a function that can't be type checked. - DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced. - STANDALONE_TERNARY, // Return value of ternary expression is discarded. + RETURN_VALUE_DISCARDED, // Function call returns something but the value isn't used. + STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself. + REDUNDANT_STATIC_UNLOAD, // The `@static_unload` annotation is used but the class does not have static data. + REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine). ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true. ASSERT_ALWAYS_FALSE, // Expression for assert argument is always false. - REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine). - EMPTY_FILE, // A script file is empty. - SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable. + INTEGER_DIVISION, // Integer divide by integer, decimal part is discarded. + NARROWING_CONVERSION, // Float value into an integer slot, precision is lost. INT_AS_ENUM_WITHOUT_CAST, // An integer value was used as an enum value without casting. INT_AS_ENUM_WITHOUT_MATCH, // An integer value was used as an enum value without matching enum member. - STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself. + EMPTY_FILE, // A script file is empty. + DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced. + RENAMED_IN_GODOT_4_HINT, // A variable or function that could not be found has been renamed in Godot 4. CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e"). - RENAMED_IN_GD4_HINT, // A variable or function that could not be found has been renamed in Godot 4 INFERENCE_ON_VARIANT, // The declaration uses type inference but the value is typed as Variant. NATIVE_METHOD_OVERRIDE, // The script method overrides a native one, this may not work as intended. GET_NODE_DEFAULT_WITHOUT_ONREADY, // A class variable uses `get_node()` (or the `$` notation) as its default value, but does not use the @onready annotation. @@ -94,38 +95,39 @@ public: WARN, // UNASSIGNED_VARIABLE_OP_ASSIGN WARN, // UNUSED_VARIABLE WARN, // UNUSED_LOCAL_CONSTANT - WARN, // SHADOWED_VARIABLE - WARN, // SHADOWED_VARIABLE_BASE_CLASS WARN, // UNUSED_PRIVATE_CLASS_VARIABLE WARN, // UNUSED_PARAMETER + WARN, // UNUSED_SIGNAL + WARN, // SHADOWED_VARIABLE + WARN, // SHADOWED_VARIABLE_BASE_CLASS + WARN, // SHADOWED_GLOBAL_IDENTIFIER WARN, // UNREACHABLE_CODE WARN, // UNREACHABLE_PATTERN WARN, // STANDALONE_EXPRESSION - WARN, // NARROWING_CONVERSION + WARN, // STANDALONE_TERNARY WARN, // INCOMPATIBLE_TERNARY - WARN, // UNUSED_SIGNAL - IGNORE, // RETURN_VALUE_DISCARDED // Too spammy by default on common cases (connect, Tween, etc.). WARN, // PROPERTY_USED_AS_FUNCTION WARN, // CONSTANT_USED_AS_FUNCTION WARN, // FUNCTION_USED_AS_PROPERTY - WARN, // INTEGER_DIVISION IGNORE, // UNSAFE_PROPERTY_ACCESS // Too common in untyped scenarios. IGNORE, // UNSAFE_METHOD_ACCESS // Too common in untyped scenarios. IGNORE, // UNSAFE_CAST // Too common in untyped scenarios. IGNORE, // UNSAFE_CALL_ARGUMENT // Too common in untyped scenarios. WARN, // UNSAFE_VOID_RETURN - WARN, // DEPRECATED_KEYWORD - WARN, // STANDALONE_TERNARY + IGNORE, // RETURN_VALUE_DISCARDED // Too spammy by default on common cases (connect, Tween, etc.). + WARN, // STATIC_CALLED_ON_INSTANCE + WARN, // REDUNDANT_STATIC_UNLOAD + WARN, // REDUNDANT_AWAIT WARN, // ASSERT_ALWAYS_TRUE WARN, // ASSERT_ALWAYS_FALSE - WARN, // REDUNDANT_AWAIT - WARN, // EMPTY_FILE - WARN, // SHADOWED_GLOBAL_IDENTIFIER + WARN, // INTEGER_DIVISION + WARN, // NARROWING_CONVERSION WARN, // INT_AS_ENUM_WITHOUT_CAST WARN, // INT_AS_ENUM_WITHOUT_MATCH - WARN, // STATIC_CALLED_ON_INSTANCE + WARN, // EMPTY_FILE + WARN, // DEPRECATED_KEYWORD + WARN, // RENAMED_IN_GODOT_4_HINT WARN, // CONFUSABLE_IDENTIFIER - WARN, // RENAMED_IN_GD4_HINT ERROR, // INFERENCE_ON_VARIANT // Most likely done by accident, usually inference is trying for a particular type. ERROR, // NATIVE_METHOD_OVERRIDE // May not work as expected. ERROR, // GET_NODE_DEFAULT_WITHOUT_ONREADY // May not work as expected. diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index 2ed444c7ad..3a5a54e275 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -337,7 +337,7 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN symbol.kind = lsp::SymbolKind::Variable; symbol.name = parameter->identifier->name; symbol.range.start.line = LINE_NUMBER_TO_INDEX(parameter->start_line); - symbol.range.start.character = LINE_NUMBER_TO_INDEX(parameter->start_line); + symbol.range.start.character = LINE_NUMBER_TO_INDEX(parameter->start_column); symbol.range.end.line = LINE_NUMBER_TO_INDEX(parameter->end_line); symbol.range.end.character = LINE_NUMBER_TO_INDEX(parameter->end_column); symbol.uri = uri; @@ -400,6 +400,20 @@ void ExtendGDScriptParser::parse_function_symbol(const GDScriptParser::FunctionN } } break; + case GDScriptParser::TypeNode::VARIABLE: { + GDScriptParser::VariableNode *variable_node = (GDScriptParser::VariableNode *)(node); + lsp::DocumentSymbol symbol; + symbol.kind = lsp::SymbolKind::Variable; + symbol.name = variable_node->identifier->name; + symbol.range.start.line = LINE_NUMBER_TO_INDEX(variable_node->start_line); + symbol.range.start.character = LINE_NUMBER_TO_INDEX(variable_node->start_column); + symbol.range.end.line = LINE_NUMBER_TO_INDEX(variable_node->end_line); + symbol.range.end.character = LINE_NUMBER_TO_INDEX(variable_node->end_column); + symbol.uri = uri; + symbol.script_path = path; + r_symbol.children.push_back(symbol); + } break; + default: continue; } diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index acd75f039a..112db4df3a 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -46,7 +46,7 @@ Error GDScriptLanguageProtocol::LSPeer::handle_data() { while (true) { if (req_pos >= LSP_MAX_BUFFER_SIZE) { req_pos = 0; - ERR_FAIL_COND_V_MSG(true, ERR_OUT_OF_MEMORY, "Response header too big"); + ERR_FAIL_V_MSG(ERR_OUT_OF_MEMORY, "Response header too big"); } Error err = connection->get_partial_data(&req_buf[req_pos], 1, read); if (err != OK) { @@ -237,6 +237,7 @@ void GDScriptLanguageProtocol::poll() { HashMap<int, Ref<LSPeer>>::Iterator E = clients.begin(); while (E != clients.end()) { Ref<LSPeer> peer = E->value; + peer->connection->poll(); StreamPeerTCP::Status status = peer->connection->get_status(); if (status == StreamPeerTCP::STATUS_NONE || status == StreamPeerTCP::STATUS_ERROR) { on_client_disconnected(E->key); diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index b90c452346..7fc2962341 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -185,15 +185,27 @@ const lsp::DocumentSymbol *GDScriptWorkspace::get_local_symbol(const ExtendGDScr const lsp::DocumentSymbol *class_symbol = &p_parser->get_symbols(); for (int i = 0; i < class_symbol->children.size(); ++i) { - if (class_symbol->children[i].kind == lsp::SymbolKind::Function || class_symbol->children[i].kind == lsp::SymbolKind::Class) { - const lsp::DocumentSymbol *function_symbol = &class_symbol->children[i]; + int kind = class_symbol->children[i].kind; + switch (kind) { + case lsp::SymbolKind::Function: + case lsp::SymbolKind::Method: + case lsp::SymbolKind::Class: { + const lsp::DocumentSymbol *function_symbol = &class_symbol->children[i]; + + for (int l = 0; l < function_symbol->children.size(); ++l) { + const lsp::DocumentSymbol *local = &function_symbol->children[l]; + if (!local->detail.is_empty() && local->name == p_symbol_identifier) { + return local; + } + } + } break; - for (int l = 0; l < function_symbol->children.size(); ++l) { - const lsp::DocumentSymbol *local = &function_symbol->children[l]; - if (!local->detail.is_empty() && local->name == p_symbol_identifier) { - return local; + case lsp::SymbolKind::Variable: { + const lsp::DocumentSymbol *variable_symbol = &class_symbol->children[i]; + if (variable_symbol->name == p_symbol_identifier) { + return variable_symbol; } - } + } break; } } @@ -650,8 +662,18 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) { symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location)); - if (symbol && symbol->kind == lsp::SymbolKind::Function && symbol->name != symbol_identifier) { - symbol = get_parameter_symbol(symbol, symbol_identifier); + if (symbol) { + switch (symbol->kind) { + case lsp::SymbolKind::Function: { + if (symbol->name != symbol_identifier) { + symbol = get_parameter_symbol(symbol, symbol_identifier); + } + } break; + + case lsp::SymbolKind::Variable: { + symbol = get_local_symbol(parser, symbol_identifier); + } break; + } } } diff --git a/modules/gdscript/language_server/godot_lsp.h b/modules/gdscript/language_server/godot_lsp.h index 8a033204da..b9a54cf818 100644 --- a/modules/gdscript/language_server/godot_lsp.h +++ b/modules/gdscript/language_server/godot_lsp.h @@ -293,16 +293,6 @@ struct WorkspaceEdit { } _FORCE_INLINE_ void add_change(const String &uri, const int &line, const int &start_character, const int &end_character, const String &new_text) { - if (HashMap<String, Vector<TextEdit>>::Iterator E = changes.find(uri)) { - Vector<TextEdit> edit_list = E->value; - for (int i = 0; i < edit_list.size(); ++i) { - TextEdit edit = edit_list[i]; - if (edit.range.start.character == start_character) { - return; - } - } - } - TextEdit new_edit; new_edit.newText = new_text; new_edit.range.start.line = line; @@ -1015,7 +1005,9 @@ struct CompletionItem { if (commitCharacters.size()) { dict["commitCharacters"] = commitCharacters; } - dict["command"] = command.to_json(); + if (!command.command.is_empty()) { + dict["command"] = command.to_json(); + } } return dict; } diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 57405aa1ce..b8448d16c2 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -350,13 +350,13 @@ void GDScriptTestRunner::handle_cmdline() { for (List<String>::Element *E = cmdline_args.front(); E; E = E->next()) { String &cmd = E->get(); if (cmd == "--gdscript-generate-tests") { - if (E->next() == nullptr) { - ERR_PRINT("Needed a path for the test files."); - exit(-1); + String path; + if (E->next()) { + path = E->next()->get(); + } else { + path = "modules/gdscript/tests/scripts"; } - const String &path = E->next()->get(); - GDScriptTestRunner runner(path, false, cmdline_args.find("--print-filenames") != nullptr); bool completed = runner.generate_outputs(); @@ -566,6 +566,14 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { ERR_FAIL_V_MSG(result, "\nCould not find test function on: '" + source_file + "'"); } + // Setup output handlers. + ErrorHandlerData error_data(&result, this); + + _print_handler.userdata = &result; + _error_handler.userdata = &error_data; + add_print_handler(&_print_handler); + add_error_handler(&_error_handler); + script->reload(); // Create object instance for test. @@ -577,14 +585,6 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { obj->set_script(script); GDScriptInstance *instance = static_cast<GDScriptInstance *>(obj->get_script_instance()); - // Setup output handlers. - ErrorHandlerData error_data(&result, this); - - _print_handler.userdata = &result; - _error_handler.userdata = &error_data; - add_print_handler(&_print_handler); - add_error_handler(&_error_handler); - // Call test function. Callable::CallError call_err; instance->callp(GDScriptTestRunner::test_function_name, nullptr, 0, call_err); diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.gd b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.gd new file mode 100644 index 0000000000..72af099158 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.gd @@ -0,0 +1,9 @@ +# GH-75870 + +const A = 1 + +class B extends A: + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.out b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.out new file mode 100644 index 0000000000..65d629a35b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_1.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Constant "A" is not a preloaded script or class. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.gd b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.gd new file mode 100644 index 0000000000..fe334f8cb7 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.gd @@ -0,0 +1,12 @@ +# GH-75870 + +class A: + const X = 1 + +const Y = A.X # A.X is now resolved. + +class B extends A.X: + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.out b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.out new file mode 100644 index 0000000000..951cfb1ea4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_non_class_constant_2.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Identifier "X" is not a preloaded script or class. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.gd b/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.gd new file mode 100644 index 0000000000..6574d4cf31 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.gd @@ -0,0 +1,9 @@ +# GH-75870 + +var A = 1 + +class B extends A: + pass + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.out b/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.out new file mode 100644 index 0000000000..7b39af6979 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/extend_variable.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot use variable "A" in extends chain. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/prints_base_type_not_found.gd b/modules/gdscript/tests/scripts/analyzer/errors/prints_base_type_not_found.gd new file mode 100644 index 0000000000..e56ae7b11d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/prints_base_type_not_found.gd @@ -0,0 +1,6 @@ +class InnerClass: + pass + +func test(): + var x : InnerClass.DoesNotExist + print("FAIL") diff --git a/modules/gdscript/tests/scripts/analyzer/errors/prints_base_type_not_found.out b/modules/gdscript/tests/scripts/analyzer/errors/prints_base_type_not_found.out new file mode 100644 index 0000000000..29c75ae3c0 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/prints_base_type_not_found.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Could not find type "DoesNotExist" under base "InnerClass". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_constructor_with_return_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_constructor_with_return_type.gd new file mode 100644 index 0000000000..3dac751ba9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_constructor_with_return_type.gd @@ -0,0 +1,5 @@ +static func _static_init() -> int: + print("static init") + +func test(): + print("done") diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_constructor_with_return_type.out b/modules/gdscript/tests/scripts/analyzer/errors/static_constructor_with_return_type.out new file mode 100644 index 0000000000..42294b7afb --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_constructor_with_return_type.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Static constructor cannot have an explicit return type. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.gd b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.gd new file mode 100644 index 0000000000..1ae814ea34 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.gd @@ -0,0 +1,9 @@ +@static_unload + +func non_static(): + return "non static" + +static var static_var = non_static() + +func test(): + print("does not run") diff --git a/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out new file mode 100644 index 0000000000..f1e9ec34f2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/static_var_init_non_static_call.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot call non-static function "non_static()" for static variable initializer. diff --git a/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.out b/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.out index 7c0416371f..f49d93620f 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.out +++ b/modules/gdscript/tests/scripts/analyzer/features/allow_void_function_to_return_void.out @@ -2,7 +2,7 @@ GDTEST_OK >> WARNING >> Line: 20 >> UNSAFE_VOID_RETURN ->> The method 'return_side_effect()' returns 'void' but it's trying to return a call to 'side_effect()' that can't be ensured to also be 'void'. +>> The method "return_side_effect()" returns "void" but it's trying to return a call to "side_effect()" that can't be ensured to also be "void". hello effect effect diff --git a/modules/gdscript/tests/scripts/analyzer/features/assert_literal_false.gd b/modules/gdscript/tests/scripts/analyzer/features/assert_literal_false.gd new file mode 100644 index 0000000000..d6c3cfc50e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/assert_literal_false.gd @@ -0,0 +1,6 @@ +func test(): + var never: Variant = false + if never: + assert(false) + assert(false, 'message') + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/assert_literal_false.out b/modules/gdscript/tests/scripts/analyzer/features/assert_literal_false.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/assert_literal_false.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.out b/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.out index 481016138a..22cd5d3a17 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.out +++ b/modules/gdscript/tests/scripts/analyzer/features/auto_inferred_type_dont_error.out @@ -2,5 +2,5 @@ GDTEST_OK >> WARNING >> Line: 6 >> UNSAFE_METHOD_ACCESS ->> The method 'free' is not present on the inferred type 'Variant' (but may be present on a subtype). +>> The method "free()" is not present on the inferred type "Variant" (but may be present on a subtype). Ok diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.out b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.out index 32e230fc80..fe6083c329 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/lambda_unused_arg.out @@ -2,4 +2,4 @@ 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' +>> The parameter "unused" is never used in the function "<anonymous lambda>()". If this is intended, prefix it with an underscore: "_unused". diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out index ff184f9f04..f861d52f2b 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/onready_with_export.out @@ -2,5 +2,5 @@ GDTEST_OK >> WARNING >> Line: 3 >> ONREADY_WITH_EXPORT ->> The "@onready" annotation will make the default value to be set after the "@export" takes effect and will override it. +>> "@onready" will set the default value after "@export" takes effect and will override it. warn diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.out b/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.out index 793faa05d4..c76950179d 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/overriding_native_method.out @@ -2,5 +2,5 @@ GDTEST_OK >> WARNING >> Line: 4 >> NATIVE_METHOD_OVERRIDE ->> The method "get" overrides a method from native class "Object". This won't be called by the engine and may not work as expected. +>> The method "get()" overrides a method from native class "Object". This won't be called by the engine and may not work as expected. warn diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out index 9d0e567534..8467697a96 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/shadowning.out @@ -2,19 +2,19 @@ GDTEST_OK >> WARNING >> Line: 5 >> SHADOWED_GLOBAL_IDENTIFIER ->> The variable 'Array' has the same name as a built-in type. +>> The variable "Array" has the same name as a built-in type. >> WARNING >> Line: 6 >> SHADOWED_GLOBAL_IDENTIFIER ->> The variable 'Node' has the same name as a global class. +>> The variable "Node" has the same name as a global class. >> WARNING >> Line: 7 >> SHADOWED_GLOBAL_IDENTIFIER ->> The variable 'is_same' has the same name as a built-in function. +>> The variable "is_same" has the same name as a built-in function. >> WARNING >> Line: 8 >> SHADOWED_GLOBAL_IDENTIFIER ->> The variable 'sqrt' has the same name as a built-in function. +>> The variable "sqrt" has the same name as a built-in function. >> WARNING >> Line: 9 >> SHADOWED_VARIABLE diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.gd b/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.gd new file mode 100644 index 0000000000..5ca8ceffdd --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.gd @@ -0,0 +1,10 @@ +# GH-72135 + +var _a +@warning_ignore("unused_private_class_variable") +var _b +@warning_ignore("unused_private_class_variable") var _c +var _d + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.out b/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.out new file mode 100644 index 0000000000..fd88d23950 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unused_private_class_variable.out @@ -0,0 +1,9 @@ +GDTEST_OK +>> WARNING +>> Line: 3 +>> UNUSED_PRIVATE_CLASS_VARIABLE +>> The class variable "_a" is declared but never used in the script. +>> WARNING +>> Line: 7 +>> UNUSED_PRIVATE_CLASS_VARIABLE +>> The class variable "_d" is declared but never used in the script. diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.gd b/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.gd new file mode 100644 index 0000000000..271a831732 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.gd @@ -0,0 +1,4 @@ +@export_enum("A",, "B", "C") var a + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.out b/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.out new file mode 100644 index 0000000000..70eee5b39f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/annotation_extra_comma.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected expression as the annotation argument. diff --git a/modules/gdscript/tests/scripts/parser/errors/duplicate_icon.gd b/modules/gdscript/tests/scripts/parser/errors/duplicate_icon.gd new file mode 100644 index 0000000000..7500e406f6 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/duplicate_icon.gd @@ -0,0 +1,5 @@ +@icon("res://1.png") +@icon("res://1.png") + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/duplicate_icon.out b/modules/gdscript/tests/scripts/parser/errors/duplicate_icon.out new file mode 100644 index 0000000000..d6cbc95d10 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/duplicate_icon.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +"@icon" annotation can only be used once. diff --git a/modules/gdscript/tests/scripts/parser/errors/duplicate_tool.gd b/modules/gdscript/tests/scripts/parser/errors/duplicate_tool.gd new file mode 100644 index 0000000000..3a2f7118f9 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/duplicate_tool.gd @@ -0,0 +1,5 @@ +@tool +@tool + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/parser/errors/duplicate_tool.out b/modules/gdscript/tests/scripts/parser/errors/duplicate_tool.out new file mode 100644 index 0000000000..26fe23fb78 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/duplicate_tool.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +"@tool" annotation can only be used once. diff --git a/modules/gdscript/tests/scripts/parser/errors/static_constructor_not_static.gd b/modules/gdscript/tests/scripts/parser/errors/static_constructor_not_static.gd new file mode 100644 index 0000000000..cbfa1f314f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/static_constructor_not_static.gd @@ -0,0 +1,5 @@ +func _static_init(): + print("static init") + +func test(): + print("done") diff --git a/modules/gdscript/tests/scripts/parser/errors/static_constructor_not_static.out b/modules/gdscript/tests/scripts/parser/errors/static_constructor_not_static.out new file mode 100644 index 0000000000..b2b8711e96 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/static_constructor_not_static.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Static constructor must be declared static. diff --git a/modules/gdscript/tests/scripts/parser/errors/static_constructor_returning_something.gd b/modules/gdscript/tests/scripts/parser/errors/static_constructor_returning_something.gd new file mode 100644 index 0000000000..711243f822 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/static_constructor_returning_something.gd @@ -0,0 +1,6 @@ +static func _static_init(): + print("static init") + return true + +func test(): + print("done") diff --git a/modules/gdscript/tests/scripts/parser/errors/static_constructor_returning_something.out b/modules/gdscript/tests/scripts/parser/errors/static_constructor_returning_something.out new file mode 100644 index 0000000000..a034850e86 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/errors/static_constructor_returning_something.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Constructor cannot return a value. diff --git a/modules/gdscript/tests/scripts/parser/features/annotations.gd b/modules/gdscript/tests/scripts/parser/features/annotations.gd new file mode 100644 index 0000000000..13c89a0a09 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/annotations.gd @@ -0,0 +1,48 @@ +extends Node + +@export_enum("A", "B", "C") var a0 +@export_enum("A", "B", "C",) var a1 + +@export_enum( + "A", + "B", + "C" +) var a2 + +@export_enum( + "A", + "B", + "C", +) var a3 + +@export +var a4: int + +@export() +var a5: int + +@export() var a6: int +@warning_ignore("onready_with_export") @onready @export var a7: int +@warning_ignore("onready_with_export") @onready() @export() var a8: int + +@warning_ignore("onready_with_export") +@onready +@export +var a9: int + +@warning_ignore("onready_with_export") +@onready() +@export() +var a10: int + +@warning_ignore("onready_with_export") +@onready() +@export() + +var a11: int + + +func test(): + for property in get_property_list(): + if property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE: + print(property) diff --git a/modules/gdscript/tests/scripts/parser/features/annotations.out b/modules/gdscript/tests/scripts/parser/features/annotations.out new file mode 100644 index 0000000000..3af0436c53 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/annotations.out @@ -0,0 +1,13 @@ +GDTEST_OK +{ "name": "a0", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 } +{ "name": "a1", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 } +{ "name": "a2", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 } +{ "name": "a3", "class_name": &"", "type": 2, "hint": 2, "hint_string": "A,B,C", "usage": 4102 } +{ "name": "a4", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a5", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a6", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a7", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a8", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a9", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a10", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } +{ "name": "a11", "class_name": &"", "type": 2, "hint": 0, "hint_string": "int", "usage": 4102 } diff --git a/modules/gdscript/tests/scripts/parser/features/constants.out b/modules/gdscript/tests/scripts/parser/features/constants.out index 6093e4a6ca..7ec33470d3 100644 --- a/modules/gdscript/tests/scripts/parser/features/constants.out +++ b/modules/gdscript/tests/scripts/parser/features/constants.out @@ -2,32 +2,32 @@ GDTEST_OK >> WARNING >> Line: 2 >> UNUSED_LOCAL_CONSTANT ->> The local constant '_TEST' is declared but never used in the block. If this is intended, prefix it with an underscore: '__TEST' +>> The local constant "_TEST" is declared but never used in the block. If this is intended, prefix it with an underscore: "__TEST". >> WARNING >> Line: 3 >> UNUSED_LOCAL_CONSTANT ->> The local constant '_STRING' is declared but never used in the block. If this is intended, prefix it with an underscore: '__STRING' +>> The local constant "_STRING" is declared but never used in the block. If this is intended, prefix it with an underscore: "__STRING". >> WARNING >> Line: 4 >> UNUSED_LOCAL_CONSTANT ->> The local constant '_VECTOR' is declared but never used in the block. If this is intended, prefix it with an underscore: '__VECTOR' +>> The local constant "_VECTOR" is declared but never used in the block. If this is intended, prefix it with an underscore: "__VECTOR". >> WARNING >> Line: 5 >> UNUSED_LOCAL_CONSTANT ->> The local constant '_ARRAY' is declared but never used in the block. If this is intended, prefix it with an underscore: '__ARRAY' +>> The local constant "_ARRAY" is declared but never used in the block. If this is intended, prefix it with an underscore: "__ARRAY". >> WARNING >> Line: 6 >> UNUSED_LOCAL_CONSTANT ->> The local constant '_DICTIONARY' is declared but never used in the block. If this is intended, prefix it with an underscore: '__DICTIONARY' +>> The local constant "_DICTIONARY" is declared but never used in the block. If this is intended, prefix it with an underscore: "__DICTIONARY". >> WARNING >> Line: 9 >> UNUSED_LOCAL_CONSTANT ->> The local constant '_HELLO' is declared but never used in the block. If this is intended, prefix it with an underscore: '__HELLO' +>> The local constant "_HELLO" is declared but never used in the block. If this is intended, prefix it with an underscore: "__HELLO". >> WARNING >> Line: 10 >> UNUSED_LOCAL_CONSTANT ->> The local constant '_INFINITY' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INFINITY' +>> The local constant "_INFINITY" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INFINITY". >> WARNING >> Line: 11 >> UNUSED_LOCAL_CONSTANT ->> The local constant '_NOT_A_NUMBER' is declared but never used in the block. If this is intended, prefix it with an underscore: '__NOT_A_NUMBER' +>> The local constant "_NOT_A_NUMBER" is declared but never used in the block. If this is intended, prefix it with an underscore: "__NOT_A_NUMBER". diff --git a/modules/gdscript/tests/scripts/parser/features/match_bind_unused.out b/modules/gdscript/tests/scripts/parser/features/match_bind_unused.out index 057c1b11e5..44d29cb82d 100644 --- a/modules/gdscript/tests/scripts/parser/features/match_bind_unused.out +++ b/modules/gdscript/tests/scripts/parser/features/match_bind_unused.out @@ -2,5 +2,5 @@ GDTEST_OK >> WARNING >> Line: 9 >> UNUSED_VARIABLE ->> The local variable 'value' is declared but never used in the block. If this is intended, prefix it with an underscore: '_value' +>> The local variable "value" is declared but never used in the block. If this is intended, prefix it with an underscore: "_value". value diff --git a/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.gd b/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.gd new file mode 100644 index 0000000000..87f9479812 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.gd @@ -0,0 +1,36 @@ +var dict = {} + +func test(): + dict.if = 1 + dict.elif = 1 + dict.else = 1 + dict.for = 1 + dict.while = 1 + dict.match = 1 + dict.break = 1 + dict.continue = 1 + dict.pass = 1 + dict.return = 1 + dict.class = 1 + dict.class_name = 1 + dict.extends = 1 + dict.is = 1 + dict.in = 1 + dict.as = 1 + dict.self = 1 + dict.signal = 1 + dict.func = 1 + dict.static = 1 + dict.const = 1 + dict.enum = 1 + dict.var = 1 + dict.breakpoint = 1 + dict.preload = 1 + dict.await = 1 + dict.yield = 1 + dict.assert = 1 + dict.void = 1 + dict.PI = 1 + dict.TAU = 1 + dict.INF = 1 + dict.NAN = 1 diff --git a/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.out b/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/reserved_keywords_as_attribute.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/parser/features/static_typing.out b/modules/gdscript/tests/scripts/parser/features/static_typing.out index 207d90fef1..40a8f97416 100644 --- a/modules/gdscript/tests/scripts/parser/features/static_typing.out +++ b/modules/gdscript/tests/scripts/parser/features/static_typing.out @@ -2,20 +2,20 @@ GDTEST_OK >> WARNING >> Line: 11 >> UNUSED_LOCAL_CONSTANT ->> The local constant '_INTEGER' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER' +>> The local constant "_INTEGER" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER". >> WARNING >> Line: 12 >> UNUSED_LOCAL_CONSTANT ->> The local constant '_INTEGER_REDUNDANT_TYPED' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER_REDUNDANT_TYPED' +>> The local constant "_INTEGER_REDUNDANT_TYPED" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_TYPED". >> WARNING >> Line: 13 >> UNUSED_LOCAL_CONSTANT ->> The local constant '_INTEGER_REDUNDANT_TYPED2' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER_REDUNDANT_TYPED2' +>> The local constant "_INTEGER_REDUNDANT_TYPED2" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_TYPED2". >> WARNING >> Line: 14 >> UNUSED_LOCAL_CONSTANT ->> The local constant '_INTEGER_REDUNDANT_INFERRED' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER_REDUNDANT_INFERRED' +>> The local constant "_INTEGER_REDUNDANT_INFERRED" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_INFERRED". >> WARNING >> Line: 15 >> UNUSED_LOCAL_CONSTANT ->> The local constant '_INTEGER_REDUNDANT_INFERRED2' is declared but never used in the block. If this is intended, prefix it with an underscore: '__INTEGER_REDUNDANT_INFERRED2' +>> The local constant "_INTEGER_REDUNDANT_INFERRED2" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_INFERRED2". diff --git a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out index e89bb9226f..f2db4e9307 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 that will be discarded if not 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/shadowed_constant.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out index 9c9417e11d..75fa01f928 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_constant.out @@ -2,7 +2,7 @@ GDTEST_OK >> WARNING >> Line: 8 >> UNUSED_LOCAL_CONSTANT ->> The local constant 'TEST' is declared but never used in the block. If this is intended, prefix it with an underscore: '_TEST' +>> The local constant "TEST" is declared but never used in the block. If this is intended, prefix it with an underscore: "_TEST". >> WARNING >> Line: 8 >> SHADOWED_VARIABLE diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_global_identifier.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_global_identifier.out index c613140eb8..75a02c5d3c 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/shadowed_global_identifier.out +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_global_identifier.out @@ -2,8 +2,8 @@ GDTEST_OK >> WARNING >> Line: 2 >> UNUSED_VARIABLE ->> The local variable 'abs' is declared but never used in the block. If this is intended, prefix it with an underscore: '_abs' +>> The local variable "abs" is declared but never used in the block. If this is intended, prefix it with an underscore: "_abs". >> WARNING >> Line: 2 >> SHADOWED_GLOBAL_IDENTIFIER ->> The variable 'abs' has the same name as a built-in function. +>> The variable "abs" has the same name as a built-in function. diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out index 82e467b368..aab27e78e2 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_class.out @@ -2,7 +2,7 @@ GDTEST_OK >> WARNING >> Line: 8 >> UNUSED_VARIABLE ->> The local variable 'foo' is declared but never used in the block. If this is intended, prefix it with an underscore: '_foo' +>> The local variable "foo" is declared but never used in the block. If this is intended, prefix it with an underscore: "_foo". >> WARNING >> Line: 8 >> SHADOWED_VARIABLE diff --git a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out index 26ce0465b1..e3cd358126 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out +++ b/modules/gdscript/tests/scripts/parser/warnings/shadowed_variable_function.out @@ -2,7 +2,7 @@ GDTEST_OK >> WARNING >> Line: 2 >> UNUSED_VARIABLE ->> The local variable 'test' is declared but never used in the block. If this is intended, prefix it with an underscore: '_test' +>> The local variable "test" is declared but never used in the block. If this is intended, prefix it with an underscore: "_test". >> WARNING >> Line: 2 >> SHADOWED_VARIABLE diff --git a/modules/gdscript/tests/scripts/parser/warnings/static_called_on_instance.out b/modules/gdscript/tests/scripts/parser/warnings/static_called_on_instance.out index 3933a35178..77994ce9ba 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/static_called_on_instance.out +++ b/modules/gdscript/tests/scripts/parser/warnings/static_called_on_instance.out @@ -2,6 +2,6 @@ GDTEST_OK >> WARNING >> Line: 11 >> STATIC_CALLED_ON_INSTANCE ->> The function 'num_uint64()' is a static function but was called from an instance. Instead, it should be directly called from the type: 'String.num_uint64()'. +>> The function "num_uint64()" is a static function but was called from an instance. Instead, it should be directly called from the type: "String.num_uint64()". 8589934592 8589934592 diff --git a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.out b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.out index cf14502e9a..10f89be132 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.out +++ b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable.out @@ -2,4 +2,4 @@ GDTEST_OK >> WARNING >> Line: 2 >> UNASSIGNED_VARIABLE ->> The variable '__' was used but never assigned a value. +>> The variable "__" was used but never assigned a value. diff --git a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.out b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.out index ba55a4e0f8..4fc91487f2 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.out +++ b/modules/gdscript/tests/scripts/parser/warnings/unassigned_variable_op_assign.out @@ -2,4 +2,4 @@ GDTEST_OK >> WARNING >> Line: 4 >> UNASSIGNED_VARIABLE_OP_ASSIGN ->> Using assignment with operation but the variable '__' was not previously assigned a value. +>> Using assignment with operation but the variable "__" was not previously assigned a value. diff --git a/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.out b/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.out index 9316abd5eb..f67dbdcd03 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.out +++ b/modules/gdscript/tests/scripts/parser/warnings/unreachable_code_after_return.out @@ -2,4 +2,4 @@ GDTEST_OK >> WARNING >> Line: 7 >> UNREACHABLE_CODE ->> Unreachable code (statement after return) in function 'test()'. +>> Unreachable code (statement after return) in function "test()". diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_argument.out b/modules/gdscript/tests/scripts/parser/warnings/unused_argument.out index 92f3308f85..3a03406f92 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/unused_argument.out +++ b/modules/gdscript/tests/scripts/parser/warnings/unused_argument.out @@ -2,4 +2,4 @@ GDTEST_OK >> WARNING >> Line: 2 >> UNUSED_PARAMETER ->> The parameter 'p_arg2' is never used in the function 'function_with_unused_argument'. If this is intended, prefix it with an underscore: '_p_arg2' +>> The parameter "p_arg2" is never used in the function "function_with_unused_argument()". If this is intended, prefix it with an underscore: "_p_arg2". diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_variable.out b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.out index 270e0e69c0..b9b3968473 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/unused_variable.out +++ b/modules/gdscript/tests/scripts/parser/warnings/unused_variable.out @@ -2,4 +2,4 @@ GDTEST_OK >> WARNING >> Line: 2 >> UNUSED_VARIABLE ->> The local variable 'unused' is declared but never used in the block. If this is intended, prefix it with an underscore: '_unused' +>> The local variable "unused" is declared but never used in the block. If this is intended, prefix it with an underscore: "_unused". diff --git a/modules/gdscript/tests/scripts/runtime/features/assign_operator.gd b/modules/gdscript/tests/scripts/runtime/features/assign_operator.gd new file mode 100644 index 0000000000..2a9fe851ef --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/assign_operator.gd @@ -0,0 +1,31 @@ +# https://github.com/godotengine/godot/issues/75832 + +@warning_ignore("narrowing_conversion") +func test(): + var hf := 2.0 + var sf = 2.0 + + var i := 2 + i *= hf + i *= sf + i *= 2.0 + print(i) + var v2 := Vector2i(1, 2) + v2 *= hf + v2 *= sf + v2 *= 2.0 + print(v2) + var v3 := Vector3i(1, 2, 3) + v3 *= hf + v3 *= sf + v3 *= 2.0 + print(v3) + var v4 := Vector4i(1, 2, 3, 4) + v4 *= hf + v4 *= sf + v4 *= 2.0 + print(v4) + + var arr := [1, 2, 3] + arr += [4, 5] + print(arr) diff --git a/modules/gdscript/tests/scripts/runtime/features/assign_operator.out b/modules/gdscript/tests/scripts/runtime/features/assign_operator.out new file mode 100644 index 0000000000..bfcfc1dff5 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/assign_operator.out @@ -0,0 +1,6 @@ +GDTEST_OK +16 +(8, 16) +(8, 16, 24) +(8, 16, 24, 32) +[1, 2, 3, 4, 5] diff --git a/modules/gdscript/tests/scripts/runtime/features/getter_with_freed_object.gd b/modules/gdscript/tests/scripts/runtime/features/getter_with_freed_object.gd new file mode 100644 index 0000000000..a2d09bf7d3 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/getter_with_freed_object.gd @@ -0,0 +1,15 @@ +# https://github.com/godotengine/godot/issues/68184 + +var node: Node: + get: + return node + set(n): + node = n + + +func test(): + node = Node.new() + node.free() + + if !is_instance_valid(node): + print("It is freed") diff --git a/modules/gdscript/tests/scripts/runtime/features/getter_with_freed_object.out b/modules/gdscript/tests/scripts/runtime/features/getter_with_freed_object.out new file mode 100644 index 0000000000..b380f593d9 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/getter_with_freed_object.out @@ -0,0 +1,2 @@ +GDTEST_OK +It is freed diff --git a/modules/gdscript/tests/scripts/runtime/features/static_constructor.gd b/modules/gdscript/tests/scripts/runtime/features/static_constructor.gd new file mode 100644 index 0000000000..e08f77df12 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/static_constructor.gd @@ -0,0 +1,13 @@ +@static_unload + +static var foo = "bar" + +static func _static_init(): + print("static init called") + prints("foo is", foo) + +func test(): + var _lambda = func _static_init(): + print("lambda does not conflict with static constructor") + + print("done") diff --git a/modules/gdscript/tests/scripts/runtime/features/static_constructor.out b/modules/gdscript/tests/scripts/runtime/features/static_constructor.out new file mode 100644 index 0000000000..7f72a0ac2c --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/static_constructor.out @@ -0,0 +1,4 @@ +GDTEST_OK +static init called +foo is bar +done diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables.gd b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd new file mode 100644 index 0000000000..e193312381 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/static_variables.gd @@ -0,0 +1,56 @@ +@static_unload + +static var perm := 0 + +static var prop := "Hello!": + get: return prop + " suffix" + set(value): prop = "prefix " + str(value) + +static func get_data(): + return "data" + +static var data = get_data() + +class Inner: + static var prop := "inner" + static func _static_init() -> void: + prints("Inner._static_init", prop) + + class InnerInner: + static var prop := "inner inner" + static func _static_init() -> void: + prints("InnerInner._static_init", prop) + +func test(): + prints("data:", data) + + prints("perm:", perm) + prints("prop:", prop) + + perm = 1 + prop = "World!" + + prints("perm:", perm) + prints("prop:", prop) + + print("other.perm:", StaticVariablesOther.perm) + print("other.prop:", StaticVariablesOther.prop) + + StaticVariablesOther.perm = 2 + StaticVariablesOther.prop = "foo" + + print("other.perm:", StaticVariablesOther.perm) + print("other.prop:", StaticVariablesOther.prop) + + @warning_ignore("unsafe_method_access") + var path = get_script().get_path().get_base_dir() + var other = load(path + "/static_variables_load.gd") + + print("load.perm:", other.perm) + print("load.prop:", other.prop) + + other.perm = 3 + other.prop = "bar" + + print("load.perm:", other.perm) + print("load.prop:", other.prop) diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables.out b/modules/gdscript/tests/scripts/runtime/features/static_variables.out new file mode 100644 index 0000000000..d2491aef5e --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/static_variables.out @@ -0,0 +1,16 @@ +GDTEST_OK +Inner._static_init inner +InnerInner._static_init inner inner +data: data +perm: 0 +prop: prefix Hello! suffix +perm: 1 +prop: prefix World! suffix +other.perm:0 +other.prop:prefix Hello! suffix +other.perm:2 +other.prop:prefix foo suffix +load.perm:0 +load.prop:prefix Hello! suffix +load.perm:3 +load.prop:prefix bar suffix diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables_load.gd b/modules/gdscript/tests/scripts/runtime/features/static_variables_load.gd new file mode 100644 index 0000000000..8913b02756 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/static_variables_load.gd @@ -0,0 +1,10 @@ +@static_unload + +static var perm := 0 + +static var prop := "Hello!": + get: return prop + " suffix" + set(value): prop = "prefix " + str(value) + +func test(): + print("ok") diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables_load.out b/modules/gdscript/tests/scripts/runtime/features/static_variables_load.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/static_variables_load.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables_other.gd b/modules/gdscript/tests/scripts/runtime/features/static_variables_other.gd new file mode 100644 index 0000000000..7a3b0acca6 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/static_variables_other.gd @@ -0,0 +1,11 @@ +@static_unload +class_name StaticVariablesOther + +static var perm := 0 + +static var prop := "Hello!": + get: return prop + " suffix" + set(value): prop = "prefix " + str(value) + +func test(): + print("ok") diff --git a/modules/gdscript/tests/scripts/runtime/features/static_variables_other.out b/modules/gdscript/tests/scripts/runtime/features/static_variables_other.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/static_variables_other.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok |